/* parser.c -- convert the command line args into an expression tree.
   Copyright (C) 1990-2021 Free Software Foundation, Inc.

   This program is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program.  If not, see <https://www.gnu.org/licenses/>.
*/

/* config.h must always come first. */
#include <config.h>

/* system headers. */
#include <assert.h>
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <grp.h>
#include <math.h>
#include <pwd.h>
#include <regex.h>
#include <sys/stat.h>
#include <unistd.h>


/* gnulib headers. */
#include "error.h"
#include "fnmatch.h"
#include "fts_.h"
#include "modechange.h"
#include "mountlist.h"
#include "parse-datetime.h"
#include "print.h"
#include "progname.h"
#include "quotearg.h"
#include "regextype.h"
#include "safe-atoi.h"
#include "selinux-at.h"
#include "splitstring.h"
#include "stat-time.h"
#include "xalloc.h"
#include "xstrtod.h"
#include "xstrtol.h"

/* At the moment, we include this after gnulib headers, since it uses
   some of the same names for function attribute macros as gnulib does,
   since I plan to make gcc-sttrigbutes a gnulib module.  However, for
   now, I haven't made the wholesale edits to gnulib that this would
   require.   Including this file last simply minimises the number of
   compiler warnings about macro redefinition (in gnulib headers).
*/
#include "gcc-function-attributes.h"

/* find headers. */
#include "buildcmd.h"
#include "defs.h"
#include "die.h"
#include "fdleak.h"
#include "findutils-version.h"
#include "system.h"


#ifndef HAVE_ENDGRENT
# define endgrent ()
#endif
#ifndef HAVE_ENDPWENT
# define endpwent ()
#endif

static bool parse_accesscheck   (const struct parser_table*, char *argv[], int *arg_ptr);
static bool parse_amin          (const struct parser_table*, char *argv[], int *arg_ptr);
static bool parse_and           (const struct parser_table*, char *argv[], int *arg_ptr);
static bool parse_anewer        (const struct parser_table*, char *argv[], int *arg_ptr);
static bool parse_cmin          (const struct parser_table*, char *argv[], int *arg_ptr);
static bool parse_cnewer        (const struct parser_table*, char *argv[], int *arg_ptr);
static bool parse_comma         (const struct parser_table*, char *argv[], int *arg_ptr);
static bool parse_daystart      (const struct parser_table*, char *argv[], int *arg_ptr);
static bool parse_delete        (const struct parser_table*, char *argv[], int *arg_ptr);
static bool parse_d             (const struct parser_table*, char *argv[], int *arg_ptr);
static bool parse_depth         (const struct parser_table*, char *argv[], int *arg_ptr);
static bool parse_empty         (const struct parser_table*, char *argv[], int *arg_ptr);
static bool parse_exec          (const struct parser_table*, char *argv[], int *arg_ptr);
static bool parse_execdir       (const struct parser_table*, char *argv[], int *arg_ptr);
static bool parse_false         (const struct parser_table*, char *argv[], int *arg_ptr);
static bool parse_fls           (const struct parser_table*, char *argv[], int *arg_ptr);
static bool parse_fprintf       (const struct parser_table*, char *argv[], int *arg_ptr);
static bool parse_follow        (const struct parser_table*, char *argv[], int *arg_ptr);
static bool parse_fprint        (const struct parser_table*, char *argv[], int *arg_ptr);
static bool parse_fprint0       (const struct parser_table*, char *argv[], int *arg_ptr);
static bool parse_fstype        (const struct parser_table*, char *argv[], int *arg_ptr);
static bool parse_gid           (const struct parser_table*, char *argv[], int *arg_ptr);
static bool parse_group         (const struct parser_table*, char *argv[], int *arg_ptr);
static bool parse_ilname        (const struct parser_table*, char *argv[], int *arg_ptr);
static bool parse_iname         (const struct parser_table*, char *argv[], int *arg_ptr);
static bool parse_inum          (const struct parser_table*, char *argv[], int *arg_ptr);
static bool parse_ipath         (const struct parser_table*, char *argv[], int *arg_ptr);
static bool parse_iregex        (const struct parser_table*, char *argv[], int *arg_ptr);
static bool parse_iwholename    (const struct parser_table*, char *argv[], int *arg_ptr);
static bool parse_links         (const struct parser_table*, char *argv[], int *arg_ptr);
static bool parse_lname         (const struct parser_table*, char *argv[], int *arg_ptr);
static bool parse_ls            (const struct parser_table*, char *argv[], int *arg_ptr);
static bool parse_maxdepth      (const struct parser_table*, char *argv[], int *arg_ptr);
static bool parse_mindepth      (const struct parser_table*, char *argv[], int *arg_ptr);
static bool parse_mmin          (const struct parser_table*, char *argv[], int *arg_ptr);
static bool parse_name          (const struct parser_table*, char *argv[], int *arg_ptr);
static bool parse_negate        (const struct parser_table*, char *argv[], int *arg_ptr);
static bool parse_newer         (const struct parser_table*, char *argv[], int *arg_ptr);
static bool parse_newerXY       (const struct parser_table*, char *argv[], int *arg_ptr);
static bool parse_noleaf        (const struct parser_table*, char *argv[], int *arg_ptr);
static bool parse_nogroup       (const struct parser_table*, char *argv[], int *arg_ptr);
static bool parse_nouser        (const struct parser_table*, char *argv[], int *arg_ptr);
static bool parse_nowarn        (const struct parser_table*, char *argv[], int *arg_ptr);
static bool parse_ok            (const struct parser_table*, char *argv[], int *arg_ptr);
static bool parse_okdir         (const struct parser_table*, char *argv[], int *arg_ptr);
static bool parse_or            (const struct parser_table*, char *argv[], int *arg_ptr);
static bool parse_path          (const struct parser_table*, char *argv[], int *arg_ptr);
static bool parse_perm          (const struct parser_table*, char *argv[], int *arg_ptr);
static bool parse_print0        (const struct parser_table*, char *argv[], int *arg_ptr);
static bool parse_printf        (const struct parser_table*, char *argv[], int *arg_ptr);
static bool parse_prune         (const struct parser_table*, char *argv[], int *arg_ptr);
static bool parse_regex         (const struct parser_table*, char *argv[], int *arg_ptr);
static bool parse_regextype     (const struct parser_table*, char *argv[], int *arg_ptr);
static bool parse_samefile      (const struct parser_table*, char *argv[], int *arg_ptr);
static bool parse_size          (const struct parser_table*, char *argv[], int *arg_ptr);
static bool parse_time          (const struct parser_table*, char *argv[], int *arg_ptr);
static bool parse_true          (const struct parser_table*, char *argv[], int *arg_ptr);
static bool parse_type          (const struct parser_table*, char *argv[], int *arg_ptr);
static bool parse_uid           (const struct parser_table*, char *argv[], int *arg_ptr);
static bool parse_used          (const struct parser_table*, char *argv[], int *arg_ptr);
static bool parse_user          (const struct parser_table*, char *argv[], int *arg_ptr);
static bool parse_wholename     (const struct parser_table*, char *argv[], int *arg_ptr);
static bool parse_xdev          (const struct parser_table*, char *argv[], int *arg_ptr);
static bool parse_ignore_race   (const struct parser_table*, char *argv[], int *arg_ptr);
static bool parse_noignore_race (const struct parser_table*, char *argv[], int *arg_ptr);
static bool parse_warn          (const struct parser_table*, char *argv[], int *arg_ptr);
static bool parse_xtype         (const struct parser_table*, char *argv[], int *arg_ptr);
static bool parse_quit          (const struct parser_table*, char *argv[], int *arg_ptr);
static bool parse_context       (const struct parser_table*, char *argv[], int *arg_ptr);
#if 0
static bool parse_show_control_chars (const struct parser_table*, char *argv[], int *arg_ptr);
#endif

static bool parse_help (const struct parser_table* entry, char **argv, int *arg_ptr)
  _GL_ATTRIBUTE_NORETURN;
static bool parse_version       (const struct parser_table*, char *argv[], int *arg_ptr)
  _GL_ATTRIBUTE_NORETURN;


static bool insert_type (char **argv, int *arg_ptr,
			 const struct parser_table *entry,
			 PRED_FUNC which_pred);
static bool insert_regex (char *argv[], int *arg_ptr,
			  const struct parser_table *entry,
			  int regex_options);
static bool insert_exec_ok (const char *action,
			    const struct parser_table *entry,
			    char *argv[],
			    int *arg_ptr);
static bool get_comp_type (const char **str,
			   enum comparison_type *comp_type);
static bool get_relative_timestamp (const char *str,
				    struct time_val *tval,
				    struct timespec origin,
				    double sec_per_unit,
				    const char *overflowmessage);
static bool get_num (const char *str,
		     uintmax_t *num,
		     enum comparison_type *comp_type);
static struct predicate* insert_num (char *argv[], int *arg_ptr,
				     const struct parser_table *entry);
static void open_output_file (const char *path, struct format_val *p);
static void open_stdout (struct format_val *p);
static bool stream_is_tty(FILE *fp);
static bool parse_noop (const struct parser_table* entry,
			char **argv, int *arg_ptr);

#define PASTE(x,y) x##y


#define PARSE_OPTION(what,suffix) \
  { (ARG_OPTION), (what), PASTE(parse_,suffix), NULL }

#define PARSE_POSOPT(what,suffix) \
  { (ARG_POSITIONAL_OPTION), (what), PASTE(parse_,suffix), NULL }

#define PARSE_TEST(what,suffix) \
  { (ARG_TEST), (what), PASTE(parse_,suffix), PASTE(pred_,suffix) }

#define PARSE_TEST_NP(what,suffix) \
  { (ARG_TEST), (what), PASTE(parse_,suffix), NULL }

#define PARSE_ACTION(what,suffix) \
  { (ARG_ACTION), (what), PASTE(parse_,suffix), PASTE(pred_,suffix) }

#define PARSE_PUNCTUATION(what,suffix) \
  { (ARG_PUNCTUATION), (what), PASTE(parse_,suffix), PASTE(pred_,suffix) }


/* Predicates we cannot handle in the usual way.  If you add an entry
 * to this table, double-check the switch statement in
 * pred_sanity_check() to make sure that the new case is being
 * correctly handled.
 */
static struct parser_table const parse_entry_newerXY =
  {
    ARG_SPECIAL_PARSE, "newerXY",            parse_newerXY, pred_newerXY /* BSD  */
  };

/* GNU find predicates that are not mentioned in POSIX.2 are marked `GNU'.
   If they are in some Unix versions of find, they are marked `Unix'. */

static struct parser_table const parse_table[] =
{
  PARSE_PUNCTUATION("!",                     negate), /* POSIX */
  PARSE_PUNCTUATION("not",                   negate),	     /* GNU */
  PARSE_PUNCTUATION("(",                     openparen), /* POSIX */
  PARSE_PUNCTUATION(")",                     closeparen), /* POSIX */
  PARSE_PUNCTUATION(",",                     comma),	     /* GNU */
  PARSE_PUNCTUATION("a",                     and), /* POSIX */
  PARSE_TEST       ("amin",                  amin),	     /* GNU */
  PARSE_PUNCTUATION("and",                   and),		/* GNU */
  PARSE_TEST       ("anewer",                anewer),	     /* GNU */
  {ARG_TEST,       "atime",                  parse_time, pred_atime}, /* POSIX */
  PARSE_TEST       ("cmin",                  cmin),	     /* GNU */
  PARSE_TEST       ("cnewer",                cnewer),	     /* GNU */
  {ARG_TEST,       "ctime",                  parse_time, pred_ctime}, /* POSIX */
  PARSE_TEST       ("context",               context),      /* GNU */
  PARSE_POSOPT     ("daystart",              daystart),	     /* GNU */
  PARSE_ACTION     ("delete",                delete), /* GNU, Mac OS, FreeBSD */
  PARSE_OPTION     ("d",                     d), /* Mac OS X, FreeBSD, NetBSD, OpenBSD, but deprecated  in favour of -depth */
  PARSE_OPTION     ("depth",                 depth), /* POSIX */
  PARSE_TEST       ("empty",                 empty),	     /* GNU */
  {ARG_ACTION,      "exec",    parse_exec, pred_exec}, /* POSIX */
  {ARG_TEST,        "executable",            parse_accesscheck, pred_executable}, /* GNU, 4.3.0+ */
  PARSE_ACTION     ("execdir",               execdir), /* *BSD, GNU */
  PARSE_ACTION     ("fls",                   fls),	     /* GNU */
  PARSE_POSOPT     ("follow",                follow),  /* GNU, Unix */
  PARSE_ACTION     ("fprint",                fprint),	     /* GNU */
  PARSE_ACTION     ("fprint0",               fprint0),	     /* GNU */
  {ARG_ACTION,      "fprintf", parse_fprintf, pred_fprintf}, /* GNU */
  PARSE_TEST       ("fstype",                fstype),  /* GNU, Unix */
  PARSE_TEST       ("gid",                   gid),	     /* GNU */
  PARSE_TEST       ("group",                 group), /* POSIX */
  PARSE_OPTION     ("ignore_readdir_race",   ignore_race),   /* GNU */
  PARSE_TEST       ("ilname",                ilname),	     /* GNU */
  PARSE_TEST       ("iname",                 iname),	     /* GNU */
  PARSE_TEST       ("inum",                  inum),    /* GNU, Unix */
  PARSE_TEST       ("ipath",                 ipath), /* GNU, deprecated in favour of iwholename */
  PARSE_TEST_NP    ("iregex",                iregex),	     /* GNU */
  PARSE_TEST_NP    ("iwholename",            iwholename),    /* GNU */
  PARSE_TEST       ("links",                 links), /* POSIX */
  PARSE_TEST       ("lname",                 lname),	     /* GNU */
  PARSE_ACTION     ("ls",                    ls),      /* GNU, Unix */
  PARSE_OPTION     ("maxdepth",              maxdepth),	     /* GNU */
  PARSE_OPTION     ("mindepth",              mindepth),	     /* GNU */
  PARSE_TEST       ("mmin",                  mmin),	     /* GNU */
  PARSE_OPTION     ("mount",                 xdev),	    /* Unix */
  {ARG_TEST,       "mtime",                  parse_time, pred_mtime}, /* POSIX */
  PARSE_TEST       ("name",                  name),
#ifdef UNIMPLEMENTED_UNIX
  PARSE(ARG_UNIMPLEMENTED, "ncpio",          ncpio),	    /* Unix */
#endif
  PARSE_TEST       ("newer",                 newer), /* POSIX */
  {ARG_TEST,       "atime",                  parse_time, pred_atime}, /* POSIX */
  PARSE_OPTION     ("noleaf",                noleaf),	     /* GNU */
  PARSE_TEST       ("nogroup",               nogroup), /* POSIX */
  PARSE_TEST       ("nouser",                nouser), /* POSIX */
  PARSE_OPTION     ("noignore_readdir_race", noignore_race), /* GNU */
  PARSE_POSOPT     ("nowarn",                nowarn),	     /* GNU */
  PARSE_POSOPT     ("warn",                  warn),	     /* GNU */
  PARSE_PUNCTUATION("o",                     or), /* POSIX */
  PARSE_PUNCTUATION("or",                    or),	     /* GNU */
  PARSE_ACTION     ("ok",                    ok), /* POSIX */
  PARSE_ACTION     ("okdir",                 okdir), /* GNU (-execdir is BSD) */
  PARSE_TEST       ("path",                  path), /* POSIX */
  PARSE_TEST       ("perm",                  perm), /* POSIX */
  PARSE_ACTION     ("print",                 print), /* POSIX */
  PARSE_ACTION     ("print0",                print0),	     /* GNU */
  {ARG_ACTION,      "printf",   parse_printf, NULL},	     /* GNU */
  PARSE_ACTION     ("prune",                 prune), /* POSIX */
  PARSE_ACTION     ("quit",                  quit),	     /* GNU */
  {ARG_TEST,       "readable",            parse_accesscheck, pred_readable}, /* GNU, 4.3.0+ */
  PARSE_TEST       ("regex",                 regex),	     /* GNU */
  PARSE_POSOPT     ("regextype",             regextype),     /* GNU */
  PARSE_TEST       ("samefile",              samefile),	     /* GNU */
#if 0
  PARSE_OPTION     ("show-control-chars",    show_control_chars), /* GNU, 4.3.0+ */
#endif
  PARSE_TEST       ("size",                  size), /* POSIX */
  PARSE_TEST       ("type",                  type), /* POSIX */
  PARSE_TEST       ("uid",                   uid),	     /* GNU */
  PARSE_TEST       ("used",                  used),	     /* GNU */
  PARSE_TEST       ("user",                  user), /* POSIX */
  PARSE_TEST_NP    ("wholename",             wholename), /* GNU, replaced -path, but now -path is standardized since POSIX 2008 */
  {ARG_TEST,       "writable",               parse_accesscheck, pred_writable}, /* GNU, 4.3.0+ */
  PARSE_OPTION     ("xdev",                  xdev), /* POSIX */
  PARSE_TEST       ("xtype",                 xtype),	     /* GNU */
#ifdef UNIMPLEMENTED_UNIX
  /* It's pretty ugly for find to know about archive formats.
     Plus what it could do with cpio archives is very limited.
     Better to leave it out.  */
  PARSE(ARG_UNIMPLEMENTED,      "cpio",                  cpio),	/* Unix */
#endif
  /* gnulib's stdbool.h might have made true and false into macros,
   * so we can't leave named 'true' and 'false' tokens, so we have
   * to expeant the relevant entries longhand.
   */
  {ARG_TEST, "false",                 parse_false,   pred_false}, /* GNU */
  {ARG_TEST, "true",                  parse_true,    pred_true }, /* GNU */
  /* Internal pseudo-option, therefore 3 minus: ---noop.  */
  {ARG_NOOP, "--noop",                NULL,          pred_true }, /* GNU, internal use only */

  /* Various other cases that don't fit neatly into our macro scheme. */
  {ARG_TEST, "help",                  parse_help,    NULL},       /* GNU */
  {ARG_TEST, "-help",                 parse_help,    NULL},       /* GNU */
  {ARG_TEST, "version",               parse_version, NULL},	  /* GNU */
  {ARG_TEST, "-version",              parse_version, NULL},	  /* GNU */
  {0, 0, 0, 0}
};


static const char *first_nonoption_arg = NULL;
static const struct parser_table *noop = NULL;

static int
fallback_getfilecon (int fd, const char *name, char **p, int prev_rv)
{
  /* Our original getfilecon () call failed.  Perhaps we can't follow a
   * symbolic link.  If that might be the problem, lgetfilecon () the link.
   * Otherwise, admit defeat. */
  switch (errno)
    {
      case ENOENT:
      case ENOTDIR:
	if (options.debug_options & DebugStat)
	  {
	    fprintf (stderr, "fallback_getfilecon(): getfilecon(%s) failed; falling "
		     "back on lgetfilecon()\n", name);
	  }
	return lgetfileconat (fd, name, p);

      case EACCES:
      case EIO:
      case ELOOP:
      case ENAMETOOLONG:
#ifdef EOVERFLOW
      case EOVERFLOW:        /* EOVERFLOW is not #defined on UNICOS. */
#endif
      default:
	return prev_rv;
    }
}

/* optionh_getfilecon () implements the getfilecon operation when the
 * -H option is in effect.
 *
 * If the item to be examined is a command-line argument, we follow
 * symbolic links.  If the getfilecon () call fails on the command-line
 * item, we fall back on the properties of the symbolic link.
 *
 * If the item to be examined is not a command-line argument, we
 * examine the link itself. */
static int
optionh_getfilecon (int fd, const char *name, char **p)
{
  int rv;
  if (0 == state.curdepth)
    {
      /* This file is from the command line; dereference the link (if it is
	 a link). */
      rv = getfileconat (fd, name, p);
      if (0 == rv)
	return 0;               /* success */
      else
	return fallback_getfilecon (fd, name, p, rv);
    }
  else
    {
      /* Not a file on the command line; do not dereference the link. */
      return lgetfileconat (fd, name, p);
    }
}

/* optionl_getfilecon () implements the getfilecon operation when the
 * -L option is in effect.  That option makes us examine the thing the
 * symbolic link points to, not the symbolic link itself. */
static int
optionl_getfilecon (int fd, const char *name, char **p)
{
  int rv = getfileconat (fd, name, p);
  if (0 == rv)
    return 0;                  /* normal case. */
  else
    return fallback_getfilecon (fd, name, p, rv);
}

/* optionp_getfilecon () implements the stat operation when the -P
 * option is in effect (this is also the default).  That option makes
 * us examine the symbolic link itself, not the thing it points to. */
static int
optionp_getfilecon (int fd, const char *name, char **p)
{
  return lgetfileconat (fd, name, p);
}

void
check_option_combinations (const struct predicate *p)
{
  enum { seen_delete=1u, seen_prune=2u };
  unsigned int predicates = 0u;

  while (p)
    {
      if (p->pred_func == pred_delete)
	predicates |= seen_delete;
      else if (p->pred_func == pred_prune)
	predicates |= seen_prune;
      p = p->pred_next;
    }

  if ((predicates & seen_prune) && (predicates & seen_delete))
    {
      /* The user specified both -delete and -prune.  One might test
       * this by first doing
       *    find dirs   .... -prune ..... -print
       * to fnd out what's going to get deleted, and then switch to
       *    find dirs   .... -prune ..... -delete
       * once we are happy.  Unfortunately, the -delete action also
       * implicitly turns on -depth, which will affect the behaviour
       * of -prune (in fact, it makes it a no-op).  In this case we
       * would like to prevent unfortunate accidents, so we require
       * the user to have explicitly used -depth.
       *
       * We only get away with this because the -delete predicate is not
       * in POSIX.   If it was, we couldn't issue a fatal error here.
       */
      if (!options.explicit_depth)
	{
	  /* This fixes Savannah bug #20865. */
	  die (EXIT_FAILURE, 0,
	       _("The -delete action automatically turns on -depth, "
		 "but -prune does nothing when -depth is in effect.  "
		 "If you want to carry on anyway, just explicitly use "
		 "the -depth option."));
	}
    }
}


static const struct parser_table*
get_noop (void)
{
  int i;
  if (NULL == noop)
    {
      for (i = 0; parse_table[i].parser_name != 0; i++)
	{
	  if (ARG_NOOP ==parse_table[i].type)
	    {
	      noop = &(parse_table[i]);
	      break;
	    }
	}
    }
  return noop;
}

static int
get_stat_Ytime (const struct stat *p,
		char what,
		struct timespec *ret)
{
  switch (what)
    {
    case 'a':
      *ret = get_stat_atime (p);
      return 1;
    case 'B':
      *ret = get_stat_birthtime (p);
      return (ret->tv_nsec >= 0);
    case 'c':
      *ret = get_stat_ctime (p);
      return 1;
    case 'm':
      *ret = get_stat_mtime (p);
      return 1;
    default:
      assert (0);
      abort ();
    }
}

void
set_follow_state (enum SymlinkOption opt)
{
  if (options.debug_options & DebugStat)
    {
      /* For DebugStat, the choice is made at runtime within debug_stat()
       * by checking the contents of the symlink_handling variable.
       */
      options.xstat = debug_stat;
    }
  else
    {
      switch (opt)
	{
	case SYMLINK_ALWAYS_DEREF:  /* -L */
	  options.xstat = optionl_stat;
	  options.x_getfilecon = optionl_getfilecon;
	  options.no_leaf_check = true;
	  break;

	case SYMLINK_NEVER_DEREF:	/* -P (default) */
	  options.xstat = optionp_stat;
	  options.x_getfilecon = optionp_getfilecon;
	  /* Can't turn no_leaf_check off because the user might have specified
	   * -noleaf anyway
	   */
	  break;

	case SYMLINK_DEREF_ARGSONLY: /* -H */
	  options.xstat = optionh_stat;
	  options.x_getfilecon = optionh_getfilecon;
	  options.no_leaf_check = true;
	}
    }
  options.symlink_handling = opt;
}


void
parse_begin_user_args (char **args, int argno,
		       const struct predicate *last,
		       const struct predicate *predicates)
{
  (void) args;
  (void) argno;
  (void) last;
  (void) predicates;
  first_nonoption_arg = NULL;
}

void
parse_end_user_args (char **args, int argno,
		     const struct predicate *last,
		     const struct predicate *predicates)
{
  /* does nothing */
  (void) args;
  (void) argno;
  (void) last;
  (void) predicates;
}

static bool
should_issue_warnings (void)
{
  if (options.posixly_correct)
    return false;
  else
    return options.warnings;
}


/* Check that it is legal to find the given primary in its
 * position and return it.
 */
static const struct parser_table*
found_parser (const char *original_arg, const struct parser_table *entry)
{
  /* If this is an option, but we have already had a
   * non-option argument, the user may be under the
   * impression that the behaviour of the option
   * argument is conditional on some preceding
   * tests.  This might typically be the case with,
   * for example, -maxdepth.
   *
   * The options -daystart and -follow are exempt
   * from this treatment, since their positioning
   * in the command line does have an effect on
   * subsequent tests but not previous ones.  That
   * might be intentional on the part of the user.
   */
  if (entry->type != ARG_POSITIONAL_OPTION)
    {
      if (entry->type == ARG_NOOP)
        return NULL;  /* internal use only, trap -noop here.  */

      /* Something other than -follow/-daystart.
       * If this is an option, check if it followed
       * a non-option and if so, issue a warning.
       */
      if (entry->type == ARG_OPTION)
	{
	  if ((first_nonoption_arg != NULL)
	      && should_issue_warnings ())
	    {
	      /* option which follows a non-option */
	      error (0, 0,
		     _("warning: you have specified the global option %s "
		       "after the argument %s, but global options are not "
		       "positional, i.e., %s affects tests specified before it "
		       "as well as those specified after it.  "
		       "Please specify global options before other arguments."),
		     original_arg,
		     first_nonoption_arg,
		     original_arg);
	    }
	}
      else
	{
	  /* Not an option or a positional option,
	   * so remember we've seen it in order to
	   * use it in a possible future warning message.
	   */
	  if (first_nonoption_arg == NULL)
	    {
	      first_nonoption_arg = original_arg;
	    }
	}
    }

  return entry;
}


/* Return a pointer to the parser function to invoke for predicate
   SEARCH_NAME.
   Return NULL if SEARCH_NAME is not a valid predicate name. */

const struct parser_table*
find_parser (const char *search_name)
{
  int i;
  const char *original_arg = search_name;

  /* Ugh.  Special case -newerXY. */
  if (0 == strncmp ("-newer", search_name, 6)
      && (8 == strlen (search_name)))
    {
      return found_parser (original_arg, &parse_entry_newerXY);
    }

  if (*search_name == '-')
    search_name++;

  for (i = 0; parse_table[i].parser_name != 0; i++)
    {
      if (strcmp (parse_table[i].parser_name, search_name) == 0)
	{
	  return found_parser (original_arg, &parse_table[i]);
	}
    }
  return NULL;
}

static float
estimate_file_age_success_rate (float num_days)
{
  if (num_days < 0.1f)
    {
      /* Assume 1% of files have timestamps in the future */
      return 0.01f;
    }
  else if (num_days < 1.0f)
    {
      /* Assume 30% of files have timestamps today */
      return 0.3f;
    }
  else if (num_days > 100.0f)
    {
      /* Assume 30% of files are very old */
      return 0.3f;
    }
  else
    {
      /* Assume 39% of files are between 1 and 100 days old. */
      return 0.39f;
    }
}

static float
estimate_timestamp_success_rate (time_t when)
{
  /* This calculation ignores the nanoseconds field of the
   * origin, but I don't think that makes much difference
   * to our estimate.
   */
  int num_days = (options.cur_day_start.tv_sec - when) / 86400;
  return estimate_file_age_success_rate (num_days);
}

/* Collect an argument from the argument list, or
 * return false.
 */
static bool
collect_arg_nonconst (char **argv, int *arg_ptr, char **collected_arg)
{
  if ((argv == NULL) || (argv[*arg_ptr] == NULL))
    {
      *collected_arg = NULL;
      return false;
    }
  else
    {
      *collected_arg = argv[*arg_ptr];
      (*arg_ptr)++;
      return true;
    }
}

static bool
collect_arg (char **argv, int *arg_ptr, const char **collected_arg)
{
  char *arg;
  const bool result = collect_arg_nonconst (argv, arg_ptr, &arg);
  *collected_arg = arg;
  return result;
}



static bool
collect_arg_stat_info (char **argv, int *arg_ptr, struct stat *p,
		       const char **argument)
{
  const char *filename;
  if (collect_arg (argv, arg_ptr, &filename))
    {
      *argument = filename;
      if (0 != (options.xstat)(filename, p))
	{
	  fatal_target_file_error (errno, filename);
	}
      return true;
    }
  else
    {
      *argument = NULL;
      return false;
    }
}

/* The parsers are responsible to continue scanning ARGV for
   their arguments.  Each parser knows what is and isn't
   allowed for itself.

   ARGV is the argument array.
   *ARG_PTR is the index to start at in ARGV,
   updated to point beyond the last element consumed.

   The predicate structure is updated with the new information. */


static bool
parse_and (const struct parser_table* entry, char **argv, int *arg_ptr)
{
  struct predicate *our_pred;

  (void) argv;
  (void) arg_ptr;

  our_pred = get_new_pred_noarg (entry);
  our_pred->pred_func = pred_and;
  our_pred->p_type = BI_OP;
  our_pred->p_prec = AND_PREC;
  our_pred->need_stat = our_pred->need_type = false;
  return true;
}

static bool
parse_anewer (const struct parser_table* entry, char **argv, int *arg_ptr)
{
  struct stat stat_newer;
  const char *arg;

  set_stat_placeholders (&stat_newer);
  if (collect_arg_stat_info (argv, arg_ptr, &stat_newer, &arg))
    {
      struct predicate *our_pred = insert_primary (entry, arg);
      our_pred->args.reftime.xval = XVAL_ATIME;
      our_pred->args.reftime.ts = get_stat_mtime (&stat_newer);
      our_pred->args.reftime.kind = COMP_GT;
      our_pred->est_success_rate = estimate_timestamp_success_rate (stat_newer.st_mtime);
      return true;
    }
  return false;
}

bool
parse_closeparen (const struct parser_table* entry, char **argv, int *arg_ptr)
{
  struct predicate *our_pred;

  (void) argv;
  (void) arg_ptr;

  our_pred = get_new_pred_noarg (entry);
  our_pred->pred_func = pred_closeparen;
  our_pred->p_type = CLOSE_PAREN;
  our_pred->p_prec = NO_PREC;
  our_pred->need_stat = our_pred->need_type = false;
  return true;
}

static bool
parse_cnewer (const struct parser_table* entry, char **argv, int *arg_ptr)
{
  struct stat stat_newer;
  const char *arg;

  set_stat_placeholders (&stat_newer);
  if (collect_arg_stat_info (argv, arg_ptr, &stat_newer, &arg))
    {
      struct predicate *our_pred = insert_primary (entry, arg);
      our_pred->args.reftime.xval = XVAL_CTIME; /* like -newercm */
      our_pred->args.reftime.ts = get_stat_mtime (&stat_newer);
      our_pred->args.reftime.kind = COMP_GT;
      our_pred->est_success_rate = estimate_timestamp_success_rate (stat_newer.st_mtime);
      return true;
    }
  return false;
}

static bool
parse_comma (const struct parser_table* entry, char **argv, int *arg_ptr)
{
  struct predicate *our_pred;

  (void) argv;
  (void) arg_ptr;

  our_pred = get_new_pred_noarg (entry);
  our_pred->pred_func = pred_comma;
  our_pred->p_type = BI_OP;
  our_pred->p_prec = COMMA_PREC;
  our_pred->need_stat = our_pred->need_type = false;
  our_pred->est_success_rate = 1.0f;
  return true;
}

static bool
parse_daystart (const struct parser_table* entry, char **argv, int *arg_ptr)
{
  struct tm *local;

  (void) entry;
  (void) argv;
  (void) arg_ptr;

  if (options.full_days == false)
    {
      options.cur_day_start.tv_sec += DAYSECS;
      options.cur_day_start.tv_nsec = 0;
      local = localtime (&options.cur_day_start.tv_sec);
      options.cur_day_start.tv_sec -= (local
				       ? (local->tm_sec + local->tm_min * 60
					  + local->tm_hour * 3600)
				       : options.cur_day_start.tv_sec % DAYSECS);
      options.full_days = true;
    }
  return true;
}

static bool
parse_delete (const struct parser_table* entry, char *argv[], int *arg_ptr)
{
  struct predicate *our_pred;
  (void) argv;
  (void) arg_ptr;

  our_pred = insert_primary_noarg (entry);
  our_pred->side_effects = our_pred->no_default_print = true;
  /* -delete implies -depth */
  options.do_dir_first = false;

  /* We do not need stat information because we check for the case
   * (errno==EISDIR) in pred_delete.
   */
  our_pred->need_stat = our_pred->need_type = false;

  our_pred->est_success_rate = 1.0f;
  return true;
}

static bool
parse_depth (const struct parser_table* entry, char **argv, int *arg_ptr)
{
  (void) entry;
  (void) argv;

  options.do_dir_first = false;
  options.explicit_depth = true;
  return parse_noop (entry, argv, arg_ptr);
}

static bool
parse_d (const struct parser_table* entry, char **argv, int *arg_ptr)
{
  if (should_issue_warnings ())
    {
      error (0, 0,
	     _("warning: the -d option is deprecated; please use "
	       "-depth instead, because the latter is a "
	       "POSIX-compliant feature."));
    }
  return parse_depth (entry, argv, arg_ptr);
}

static bool
parse_empty (const struct parser_table* entry, char **argv, int *arg_ptr)
{
  struct predicate *our_pred;
  (void) argv;
  (void) arg_ptr;

  our_pred = insert_primary_noarg (entry);
  our_pred->est_success_rate = 0.01f; /* assume 1% of files are empty. */
  return true;
}

static bool
parse_exec (const struct parser_table* entry, char **argv, int *arg_ptr)
{
  return insert_exec_ok ("-exec", entry, argv, arg_ptr);
}

static bool
parse_execdir (const struct parser_table* entry, char **argv, int *arg_ptr)
{
  return insert_exec_ok ("-execdir", entry, argv, arg_ptr);
}

static bool
insert_false(void)
{
  struct predicate *our_pred;
  const struct parser_table *entry_false;

  entry_false = find_parser("false");
  our_pred = insert_primary_noarg (entry_false);
  our_pred->need_stat = our_pred->need_type = false;
  our_pred->side_effects = our_pred->no_default_print = false;
  our_pred->est_success_rate = 0.0f;
  return true;
}


static bool
parse_false (const struct parser_table* entry, char **argv, int *arg_ptr)
{
  (void) entry;
  (void) argv;
  (void) arg_ptr;
  return insert_false ();
}

static bool
insert_fls (const struct parser_table* entry, const char *filename)
{
  struct predicate *our_pred = insert_primary_noarg (entry);
  if (filename)
    open_output_file (filename, &our_pred->args.printf_vec);
  else
    open_stdout (&our_pred->args.printf_vec);
  our_pred->side_effects = our_pred->no_default_print = true;
  our_pred->est_success_rate = 1.0f;
  return true;
}


static bool
parse_fls (const struct parser_table* entry, char **argv, int *arg_ptr)
{
  const char *filename;
  if (collect_arg (argv, arg_ptr, &filename))
    {
      if (insert_fls (entry, filename))
	return true;
      else
	--*arg_ptr;		/* don't consume the invalid arg. */
    }
  return false;
}

static bool
parse_follow (const struct parser_table* entry, char **argv, int *arg_ptr)
{
  set_follow_state (SYMLINK_ALWAYS_DEREF);
  return parse_noop (entry, argv, arg_ptr);
}

static bool
parse_fprint (const struct parser_table* entry, char **argv, int *arg_ptr)
{
  struct predicate *our_pred;
  const char *filename;
  if (collect_arg (argv, arg_ptr, &filename))
    {
      our_pred = insert_primary (entry, filename);
      open_output_file (filename, &our_pred->args.printf_vec);
      our_pred->side_effects = our_pred->no_default_print = true;
      our_pred->need_stat = our_pred->need_type = false;
      our_pred->est_success_rate = 1.0f;
      return true;
    }
  else
    {
      return false;
    }
}

static bool
insert_fprint (const struct parser_table* entry, const char *filename)
{
  struct predicate *our_pred = insert_primary (entry, filename);
  if (filename)
    open_output_file (filename, &our_pred->args.printf_vec);
  else
    open_stdout (&our_pred->args.printf_vec);
  our_pred->side_effects = our_pred->no_default_print = true;
  our_pred->need_stat = our_pred->need_type = false;
  our_pred->est_success_rate = 1.0f;
  return true;
}


static bool
parse_fprint0 (const struct parser_table* entry, char **argv, int *arg_ptr)
{
  const char *filename;
  if (collect_arg (argv, arg_ptr, &filename))
    {
      if (insert_fprint (entry, filename))
	return true;
      else
	--*arg_ptr;		/* don't consume the bad arg. */
    }
  return false;
}

static float estimate_fstype_success_rate (const char *fsname)
{
  struct stat dir_stat;
  const char *the_root_dir = "/";
  if (0 == stat (the_root_dir, &dir_stat)) /* it's an absolute path anyway */
    {
      const char *fstype = filesystem_type (&dir_stat, the_root_dir);
      /* Assume most files are on the same file system type as the root fs. */
      if (0 == strcmp (fsname, fstype))
	  return 0.7f;
      else
	return 0.3f;
    }
  return 1.0f;
}



static bool
parse_fstype (const struct parser_table* entry, char **argv, int *arg_ptr)
{
  const char *typename;
  if (collect_arg (argv, arg_ptr, &typename))
    {
      if (options.optimisation_level < 2 || is_used_fs_type (typename))
	{
	  struct predicate *our_pred = insert_primary (entry, typename);
	  our_pred->args.str = typename;

	  /* This is an expensive operation, so although there are
	   * circumstances where it is selective, we ignore this fact
	   * because we probably don't want to promote this test to the
	   * front anyway.
	   */
	  our_pred->est_success_rate = estimate_fstype_success_rate (typename);
	  return true;
	}
      else
	{
	  /* This filesystem type is not listed in the mount table.
	   * Hence this predicate will always return false (with this argument).
	   * Substitute a predicate with the same effect as -false.
	   */
	  if (options.debug_options & DebugTreeOpt)
	    {
	      fprintf (stderr,
		       "-fstype %s can never succeed, substituting -false\n",
		       typename);
	    }
	  return insert_false ();
	}
    }
  else
    {
      return false;
    }
}

static bool
parse_gid (const struct parser_table* entry, char **argv, int *arg_ptr)
{
  struct predicate *p = insert_num (argv, arg_ptr, entry);
  if (p)
    {
      p->est_success_rate = (p->args.numinfo.l_val < 100) ? 0.99 : 0.2;
      return true;
    }
  else
    {
      --*arg_ptr;		/* don't consume the invalid argument. */
      return false;
    }
}


static bool
parse_group (const struct parser_table* entry, char **argv, int *arg_ptr)
{
  const char *groupname;
  const int saved_argc = *arg_ptr;

  if (collect_arg (argv, arg_ptr, &groupname))
    {
      gid_t gid;
      struct predicate *our_pred;
      struct group *cur_gr = getgrnam (groupname);
      endgrent ();
      if (cur_gr)
	{
	  gid = cur_gr->gr_gid;
	}
      else
	{
	  const int gid_len = strspn (groupname, "0123456789");
	  if (gid_len)
	    {
	      if (groupname[gid_len] == 0)
		{
		  gid = safe_atoi (groupname, options.err_quoting_style);
		}
	      else
		{
		  /* XXX: no test in test suite for this */
		  die (EXIT_FAILURE, 0,
		       _("%s is not the name of an existing group and"
			 " it does not look like a numeric group ID "
			 "because it has the unexpected suffix %s"),
		       quotearg_n_style (0, options.err_quoting_style, groupname),
		       quotearg_n_style (1, options.err_quoting_style, groupname+gid_len));
		  *arg_ptr = saved_argc; /* don't consume the invalid argument. */
		  return false;
		}
	    }
	  else
	    {
	      if (*groupname)
		{
		  /* XXX: no test in test suite for this */
		  die (EXIT_FAILURE, 0,
		       _("%s is not the name of an existing group"),
		       quotearg_n_style (0, options.err_quoting_style, groupname));
		}
	      else
		{
		  die (EXIT_FAILURE, 0,
		       _("argument to -group is empty, but should be a group name"));
		}
	      *arg_ptr = saved_argc; /* don't consume the invalid argument. */
	      return false;
	    }
	}
      our_pred = insert_primary (entry, groupname);
      our_pred->args.gid = gid;
      our_pred->est_success_rate = (our_pred->args.numinfo.l_val < 100) ? 0.99 : 0.2;
      return true;
    }
  return false;
}

static bool
parse_help (const struct parser_table* entry, char **argv, int *arg_ptr)
{
  (void) entry;
  (void) argv;
  (void) arg_ptr;

  usage (EXIT_SUCCESS);
}

static float
estimate_pattern_match_rate (const char *pattern, int is_regex)
{
  if (strpbrk (pattern, "*?[") || (is_regex && strpbrk(pattern, ".")))
    {
      /* A wildcard; assume the pattern matches most files. */
      return 0.8f;
    }
  else
    {
      return 0.1f;
    }
}

static bool
parse_ilname (const struct parser_table* entry, char **argv, int *arg_ptr)
{
  const char *name;
  if (collect_arg (argv, arg_ptr, &name))
    {
      struct predicate *our_pred = insert_primary (entry, name);
      our_pred->args.str = name;
      /* Use the generic glob pattern estimator to figure out how many
       * links will match, but bear in mind that most files won't be links.
       */
      our_pred->est_success_rate = 0.1f * estimate_pattern_match_rate (name, 0);
      return true;
    }
  else
    {
      return false;
    }
}


/* sanity check the fnmatch() function to make sure that case folding
 * is supported (as opposed to just having the flag ignored).
 */
static bool
fnmatch_sanitycheck (void)
{
  static bool checked = false;
  if (!checked)
    {
      if (0 != fnmatch ("foo", "foo", 0)
	  || 0 == fnmatch ("Foo", "foo", 0)
	  || 0 != fnmatch ("Foo", "foo", FNM_CASEFOLD))
	{
	  die (EXIT_FAILURE, 0,
	       _("sanity check of the fnmatch() library function failed."));
	  return false;
	}
      checked = true;
    }
  return checked;
}


static bool
check_name_arg (const char *pred, const char *alt, const char *arg)
{
  if (should_issue_warnings () && strchr (arg, '/'))
    {
      error (0, 0,
	     _("warning: %s matches against basenames only, "
	       "but the given pattern contains a directory separator (%s), "
	       "thus the expression will evaluate to false all the time.  "
	       "Did you mean %s?"),
	    safely_quote_err_filename (0, pred),
	    safely_quote_err_filename (1, "/"),
	    safely_quote_err_filename (2, alt));
    }
  return true;			/* allow it anyway */
}



static bool
parse_iname (const struct parser_table* entry, char **argv, int *arg_ptr)
{
  const char *name;
  fnmatch_sanitycheck ();
  if (collect_arg (argv, arg_ptr, &name))
    {
      if (check_name_arg ("-iname", "-iwholename", name))
	{
	  struct predicate *our_pred = insert_primary (entry, name);
	  our_pred->need_stat = our_pred->need_type = false;
	  our_pred->args.str = name;
	  our_pred->est_success_rate = estimate_pattern_match_rate (name, 0);
	  return true;
	}
    }
  return false;
}

static bool
parse_inum (const struct parser_table* entry, char **argv, int *arg_ptr)
{
  struct predicate *p =  insert_num (argv, arg_ptr, entry);
  if (p)
    {
      /* inode number is exact match only, so very low proportions of
       * files match
       */
      p->est_success_rate = 1e-6;
      p->need_inum = true;
      p->need_stat = false;
      p->need_type = false;
      return true;
    }
  else
    {
      --*arg_ptr;		/* don't consume the invalid argument. */
      return false;
    }
}

static bool
parse_iregex (const struct parser_table* entry, char **argv, int *arg_ptr)
{
  return insert_regex (argv, arg_ptr, entry, RE_ICASE|options.regex_options);
}

static bool
parse_links (const struct parser_table* entry, char **argv, int *arg_ptr)
{
  struct predicate *p = insert_num (argv, arg_ptr, entry);
  if (p)
    {
      if (p->args.numinfo.l_val == 1)
	p->est_success_rate = 0.99;
      else if (p->args.numinfo.l_val == 2)
	p->est_success_rate = 0.01;
      else
	p->est_success_rate = 1e-3;
      return true;
    }
  else
    {
      --*arg_ptr;		/* don't consume the invalid argument. */
      return false;
    }
}

static bool
parse_lname (const struct parser_table* entry, char **argv, int *arg_ptr)
{
  const char *name;
  fnmatch_sanitycheck ();
  if (collect_arg (argv, arg_ptr, &name))
    {
      struct predicate *our_pred = insert_primary (entry, name);
      our_pred->args.str = name;
      our_pred->est_success_rate = 0.1f * estimate_pattern_match_rate (name, 0);
      return true;
    }
  return false;
}

static bool
parse_ls (const struct parser_table* entry, char **argv, int *arg_ptr)
{
  (void) &argv;
  (void) &arg_ptr;
  return insert_fls (entry, NULL);
}

static bool
insert_depthspec (const struct parser_table* entry, char **argv, int *arg_ptr,
		  int *limitptr)
{
  const char *depthstr;
  int depth_len;
  const char *predicate = argv[(*arg_ptr)-1];
  if (collect_arg (argv, arg_ptr, &depthstr))
    {
      depth_len = strspn (depthstr, "0123456789");
      if ((depth_len > 0) && (depthstr[depth_len] == 0))
	{
	  (*limitptr) = safe_atoi (depthstr, options.err_quoting_style);
	  if (*limitptr >= 0)
	    {
	      return parse_noop (entry, argv, arg_ptr);
	    }
	}
      die (EXIT_FAILURE, 0,
	   _("Expected a positive decimal integer argument to %s, but got %s"),
	   predicate,
	   quotearg_n_style (0, options.err_quoting_style, depthstr));
      /* NOTREACHED */
      return false;
    }
  /* missing argument */
  return false;
}


static bool
parse_maxdepth (const struct parser_table* entry, char **argv, int *arg_ptr)
{
  return insert_depthspec (entry, argv, arg_ptr, &options.maxdepth);
}

static bool
parse_mindepth (const struct parser_table* entry, char **argv, int *arg_ptr)
{
  return insert_depthspec (entry, argv, arg_ptr, &options.mindepth);
}


static bool
do_parse_xmin (const struct parser_table* entry,
	       char **argv,
	       int *arg_ptr,
	       enum xval xv)
{
  const char *minutes;
  const int saved_argc = *arg_ptr;

  if (collect_arg (argv, arg_ptr, &minutes))
    {
      struct time_val tval;
      struct timespec origin = options.cur_day_start;
      tval.xval = xv;
      origin.tv_sec += DAYSECS;
      if (get_relative_timestamp (minutes, &tval, origin, 60,
				  "arithmetic overflow while converting %s "
				  "minutes to a number of seconds"))
	{
	  struct predicate *our_pred = insert_primary (entry, minutes);
	  our_pred->args.reftime = tval;
	  our_pred->est_success_rate = estimate_timestamp_success_rate (tval.ts.tv_sec);
	  return true;
	}
      else
	{
	  /* Don't consume the invalid argument. */
	  *arg_ptr = saved_argc;
	}
    }
  return false;
}
static bool
parse_amin (const struct parser_table* entry, char **argv, int *arg_ptr)
{
  return do_parse_xmin (entry, argv, arg_ptr, XVAL_ATIME);
}

static bool
parse_cmin (const struct parser_table* entry, char **argv, int *arg_ptr)
{
  return do_parse_xmin (entry, argv, arg_ptr, XVAL_CTIME);
}


static bool
parse_mmin (const struct parser_table* entry, char **argv, int *arg_ptr)
{
  return do_parse_xmin (entry, argv, arg_ptr, XVAL_MTIME);
}

static bool
parse_name (const struct parser_table* entry, char **argv, int *arg_ptr)
{
  const char *name;
  const int saved_argc = *arg_ptr;

  if (collect_arg (argv, arg_ptr, &name))
    {
      fnmatch_sanitycheck ();
      if (check_name_arg ("-name", "-wholename", name))
	{
	  struct predicate *our_pred = insert_primary (entry, name);
	  our_pred->need_stat = our_pred->need_type = false;
	  our_pred->args.str = name;
	  our_pred->est_success_rate = estimate_pattern_match_rate (name, 0);
	  return true;
	}
      else
	{
	  *arg_ptr = saved_argc; /* don't consume the invalid argument. */
	}
    }
  return false;
}

static bool
parse_negate (const struct parser_table* entry, char **argv, int *arg_ptr)
{
  struct predicate *our_pred;

  (void) &argv;
  (void) &arg_ptr;

  our_pred = get_new_pred_chk_op (entry, NULL);
  our_pred->pred_func = pred_negate;
  our_pred->p_type = UNI_OP;
  our_pred->p_prec = NEGATE_PREC;
  our_pred->need_stat = our_pred->need_type = false;
  return true;
}

static bool
parse_newer (const struct parser_table* entry, char **argv, int *arg_ptr)
{
  struct predicate *our_pred;
  struct stat stat_newer;
  const char *arg;

  set_stat_placeholders (&stat_newer);
  if (collect_arg_stat_info (argv, arg_ptr, &stat_newer, &arg))
    {
      our_pred = insert_primary (entry, arg);
      our_pred->args.reftime.ts = get_stat_mtime (&stat_newer);
      our_pred->args.reftime.xval = XVAL_MTIME;
      our_pred->args.reftime.kind = COMP_GT;
      our_pred->est_success_rate = estimate_timestamp_success_rate (stat_newer.st_mtime);
      return true;
    }
  return false;
}


static bool
parse_newerXY (const struct parser_table* entry, char **argv, int *arg_ptr)
{
  (void) argv;
  (void) arg_ptr;

  if ((argv == NULL) || (argv[*arg_ptr] == NULL))
    {
      return false;
    }
  else if (8u != strlen (argv[*arg_ptr]))
    {
      return false;
    }
  else
    {
      char x, y;
      const char validchars[] = "aBcmt";

      assert (0 == strncmp ("-newer", argv[*arg_ptr], 6));
      x = argv[*arg_ptr][6];
      y = argv[*arg_ptr][7];


#if !defined HAVE_STRUCT_STAT_ST_BIRTHTIME && !defined HAVE_STRUCT_STAT_ST_BIRTHTIMENSEC && !defined HAVE_STRUCT_STAT_ST_BIRTHTIMESPEC_TV_NSEC && !defined HAVE_STRUCT_STAT_ST_BIRTHTIM_TV_NSEC
      if ('B' == x || 'B' == y)
	{
	  error (0, 0,
		 _("This system does not provide a way to find the birth time of a file."));
	  return false;
	}
#endif

      /* -newertY (for any Y) is invalid. */
      if (x == 't'
	  || (NULL == strchr (validchars, x))
	  || (NULL == strchr ( validchars, y)))
	{
	  return false;
	}
      else
	{
	  struct predicate *our_pred;

	  /* Because this item is ARG_SPECIAL_PARSE, we have to advance arg_ptr
	   * past the test name (for most other tests, this is already done)
	   */
	  if (argv[1+*arg_ptr] == NULL)
	    {
	      die (EXIT_FAILURE, 0, _("The %s test needs an argument"),
		   quotearg_n_style (0, options.err_quoting_style, argv[*arg_ptr]));
	    }
	  else
	    {
	      (*arg_ptr)++;
	    }

	  our_pred = insert_primary (entry, argv[*arg_ptr]);


	  switch (x)
	    {
	    case 'a':
	      our_pred->args.reftime.xval = XVAL_ATIME;
	      break;
	    case 'B':
	      our_pred->args.reftime.xval = XVAL_BIRTHTIME;
	      break;
	    case 'c':
	      our_pred->args.reftime.xval = XVAL_CTIME;
	      break;
	    case 'm':
	      our_pred->args.reftime.xval = XVAL_MTIME;
	      break;
	    default:
	      assert (strchr (validchars, x));
	      assert (0);
	    }

	  if ('t' == y)
	    {
	      if (!parse_datetime (&our_pred->args.reftime.ts,
				   argv[*arg_ptr],
				   &options.start_time))
		{
		  die (EXIT_FAILURE, 0,
		       _("I cannot figure out how to interpret %s as a date or time"),
		       quotearg_n_style (0, options.err_quoting_style, argv[*arg_ptr]));
		}
	    }
	  else
	    {
	      struct stat stat_newer;

	      /* Stat the named file. */
	      set_stat_placeholders (&stat_newer);
	      if ((*options.xstat) (argv[*arg_ptr], &stat_newer))
		fatal_target_file_error (errno, argv[*arg_ptr]);

	      if (!get_stat_Ytime (&stat_newer, y, &our_pred->args.reftime.ts))
		{
		  /* We cannot extract a timestamp from the struct stat. */
		  die (EXIT_FAILURE, 0,
		       _("Cannot obtain birth time of file %s"),
		       safely_quote_err_filename (0, argv[*arg_ptr]));
		}
	    }
	  our_pred->args.reftime.kind = COMP_GT;
	  our_pred->est_success_rate = estimate_timestamp_success_rate (our_pred->args.reftime.ts.tv_sec);
	  (*arg_ptr)++;

	  assert (our_pred->pred_func != NULL);
	  assert (our_pred->pred_func == pred_newerXY);
	  assert (our_pred->need_stat);
	  return true;
	}
    }
}


static bool
parse_noleaf (const struct parser_table* entry, char **argv, int *arg_ptr)
{
  options.no_leaf_check = true;
  return parse_noop (entry, argv, arg_ptr);
}

static bool
parse_nogroup (const struct parser_table* entry, char **argv, int *arg_ptr)
{
  struct predicate *our_pred;

  (void) &argv;
  (void) &arg_ptr;

  our_pred = insert_primary (entry, NULL);
  our_pred->est_success_rate = 1e-4;
  return true;
}

static bool
parse_nouser (const struct parser_table* entry, char **argv, int *arg_ptr)
{
  struct predicate *our_pred;
  (void) argv;
  (void) arg_ptr;


  our_pred = insert_primary_noarg (entry);
  our_pred->est_success_rate = 1e-3;
  return true;
}

static bool
parse_nowarn (const struct parser_table* entry, char **argv, int *arg_ptr)
{
  options.warnings = false;
  return parse_noop (entry, argv, arg_ptr);
}

static bool
parse_ok (const struct parser_table* entry, char **argv, int *arg_ptr)
{
  return insert_exec_ok ("-ok", entry, argv, arg_ptr);
}

static bool
parse_okdir (const struct parser_table* entry, char **argv, int *arg_ptr)
{
  return insert_exec_ok ("-okdir", entry, argv, arg_ptr);
}

bool
parse_openparen (const struct parser_table* entry, char **argv, int *arg_ptr)
{
  struct predicate *our_pred;

  (void) argv;
  (void) arg_ptr;

  our_pred = get_new_pred_chk_op (entry, NULL);
  our_pred->pred_func = pred_openparen;
  our_pred->p_type = OPEN_PAREN;
  our_pred->p_prec = NO_PREC;
  our_pred->need_stat = our_pred->need_type = false;
  return true;
}

static bool
parse_or (const struct parser_table* entry, char **argv, int *arg_ptr)
{
  struct predicate *our_pred;

  (void) argv;
  (void) arg_ptr;

  our_pred = get_new_pred_noarg (entry);
  our_pred->pred_func = pred_or;
  our_pred->p_type = BI_OP;
  our_pred->p_prec = OR_PREC;
  our_pred->need_stat = our_pred->need_type = false;
  return true;
}

static bool
is_feasible_path_argument (const char *arg, bool foldcase)
{
  const char *last = strrchr (arg, '/');
  if (last && !last[1])
    {
      /* The name ends with "/". */
      if (matches_start_point (arg, foldcase))
	{
	  /* "-path foo/" can succeed if one of the start points is "foo/". */
	  return true;
	}
      else
	{
	  return false;
	}
    }
  return true;
}


static bool
insert_path_check (const struct parser_table* entry, char **argv, int *arg_ptr,
		   const char *predicate_name, PREDICATEFUNCTION pred)
{
  const char *name;
  bool foldcase = false;

  if (pred == pred_ipath)
    foldcase = true;

  fnmatch_sanitycheck ();

  if (collect_arg (argv, arg_ptr, &name))
    {
      struct predicate *our_pred = insert_primary_withpred (entry, pred, name);
      our_pred->need_stat = our_pred->need_type = false;
      our_pred->args.str = name;
      our_pred->est_success_rate = estimate_pattern_match_rate (name, 0);

      if (!options.posixly_correct
	  && !is_feasible_path_argument (name, foldcase))
	{
	  error (0, 0, _("warning: -%s %s will not match anything "
			 "because it ends with /."),
		 predicate_name, name);
	  our_pred->est_success_rate = 1.0e-8;
	}
      return true;
    }
  return false;
}

/* For some time, -path was deprecated (at RMS's request) in favour of
 * -iwholename.  See the node "GNU Manuals" in standards.texi for the
 * rationale for this (basically, GNU prefers the use of the phrase
 * "file name" to "path name".
 *
 * We do not issue a warning that this usage is deprecated
 * since it is standardized since POSIX 2008.
 */
static bool
parse_path (const struct parser_table* entry, char **argv, int *arg_ptr)
{
  return insert_path_check (entry, argv, arg_ptr, "path", pred_path);
}

static bool
parse_wholename (const struct parser_table* entry, char **argv, int *arg_ptr)
{
  return insert_path_check (entry, argv, arg_ptr, "wholename", pred_path);
}

/* -ipath was deprecated (at RMS's request) in favour of
 * -iwholename.   See the node "GNU Manuals" in standards.texi
 * for the rationale for this (basically, GNU prefers the use
 * of the phrase "file name" to "path name".
 * However, -path is now standardised so I un-deprecated -ipath.
 */
static bool
parse_ipath (const struct parser_table* entry, char **argv, int *arg_ptr)
{
  return insert_path_check (entry, argv, arg_ptr, "ipath", pred_ipath);
}

static bool
parse_iwholename (const struct parser_table* entry, char **argv, int *arg_ptr)
{
  return insert_path_check (entry, argv, arg_ptr, "iwholename", pred_ipath);
}


static bool
parse_perm (const struct parser_table* entry, char **argv, int *arg_ptr)
{
  mode_t perm_val[2];
  float rate;
  int mode_start = 0;
  enum permissions_type kind = PERM_EXACT;
  struct mode_change *change;
  struct predicate *our_pred;
  const char *perm_expr;

  if (!collect_arg (argv, arg_ptr, &perm_expr))
    return false;

  switch (perm_expr[0])
    {
    case '-':
      mode_start = 1;
      kind = PERM_AT_LEAST;
      rate = 0.2;
      break;

    case '/':			/* GNU extension */
      mode_start = 1;
      kind = PERM_ANY;
      rate = 0.3;
      break;

    default:
      /* For example, '-perm 0644', which is valid and matches
       * only files whose mode is exactly 0644.
       */
      mode_start = 0;
      kind = PERM_EXACT;
      rate = 0.01;
      break;
    }

  change = mode_compile (perm_expr + mode_start);

  /* Reject invalid modes, or modes of the form +NUMERICMODE.
     The latter were formerly accepted as a GNU extension, but that
     extension was incompatible with how GNU 'chmod' treats these modes now,
     and it would be confusing if 'find' continued to support it.  */
  if (NULL == change
      || (perm_expr[0] == '+' && '0' <= perm_expr[1] && perm_expr[1] < '8'))
    die (EXIT_FAILURE, 0, _("invalid mode %s"),
	 quotearg_n_style (0, options.err_quoting_style, perm_expr));
  perm_val[0] = mode_adjust (0, false, 0, change, NULL);
  perm_val[1] = mode_adjust (0, true, 0, change, NULL);
  free (change);

  if (('/' == perm_expr[0]) && (0 == perm_val[0]) && (0 == perm_val[1]))
    {
      /* The meaning of -perm /000 will change in the future.  It
       * currently matches no files, but like -perm -000 it should
       * match all files.
       *
       * Starting in 2005, we used to issue a warning message
       * informing the user that the behaviour would change in the
       * future.  We have now changed the behaviour and issue a
       * warning message that the behaviour recently changed.
       */
      error (0, 0,
	     _("warning: you have specified a mode pattern %s (which is "
	       "equivalent to /000). The meaning of -perm /000 has now been "
	       "changed to be consistent with -perm -000; that is, while it "
	       "used to match no files, it now matches all files."),
	     perm_expr);

      kind = PERM_AT_LEAST;

      /* The "magic" number below is just the fraction of files on my
       * own system that "-type l -xtype l" fails for (i.e. unbroken symlinks).
       * Actual totals are 1472 and 1073833.
       */
      rate = 0.9986; /* probably matches anything but a broken symlink */
    }

  our_pred = insert_primary (entry, perm_expr);
  our_pred->est_success_rate = rate;
  our_pred->args.perm.kind = kind;
  memcpy (our_pred->args.perm.val, perm_val, sizeof perm_val);
  return true;
}

bool
parse_print (const struct parser_table* entry, char **argv, int *arg_ptr)
{
  struct predicate *our_pred;

  (void) argv;
  (void) arg_ptr;

  our_pred = insert_primary_noarg (entry);
  /* -print has the side effect of printing.  This prevents us
     from doing undesired multiple printing when the user has
     already specified -print. */
  our_pred->side_effects = our_pred->no_default_print = true;
  our_pred->need_stat = our_pred->need_type = false;
  open_stdout (&our_pred->args.printf_vec);
  return true;
}

static bool
parse_print0 (const struct parser_table* entry, char **argv, int *arg_ptr)
{
  (void) entry;
  (void) argv;
  (void) arg_ptr;
  return insert_fprint (entry, NULL);
}

static bool
parse_printf (const struct parser_table* entry, char **argv, int *arg_ptr)
{
  char *format;
  const int saved_argc = *arg_ptr;

  if (collect_arg_nonconst (argv, arg_ptr, &format))
    {
      struct format_val fmt;
      open_stdout (&fmt);
      if (insert_fprintf (&fmt, entry, format))
	{
	  return true;
	}
      else
	{
	  *arg_ptr = saved_argc; /* don't consume the invalid argument. */
	  return false;
	}
    }
  return false;
}

static bool
parse_fprintf (const struct parser_table* entry, char **argv, int *arg_ptr)
{
  const char *filename;
  char *format;
  int saved_argc = *arg_ptr;

  if (collect_arg (argv, arg_ptr, &filename))
    {
      if (collect_arg_nonconst (argv, arg_ptr, &format))
	{
	  struct format_val fmt;
	  open_output_file (filename, &fmt);
	  saved_argc = *arg_ptr;

	  if (insert_fprintf (&fmt, entry, format))
	    return true;
	}
    }
  *arg_ptr = saved_argc; /* don't consume the invalid argument. */
  return false;
}

static bool
parse_prune (const struct parser_table* entry, char **argv, int *arg_ptr)
{
  struct predicate *our_pred;

  (void) argv;
  (void) arg_ptr;

  our_pred = insert_primary_noarg (entry);
  if (options.do_dir_first == false)
    our_pred->need_stat = our_pred->need_type = false;
  /* -prune has a side effect that it does not descend into
     the current directory. */
  our_pred->side_effects = true;
  our_pred->no_default_print = false;
  return true;
}

static bool
parse_quit  (const struct parser_table* entry, char **argv, int *arg_ptr)
{
  struct predicate *our_pred = insert_primary_noarg (entry);
  (void) argv;
  (void) arg_ptr;
  our_pred->need_stat = our_pred->need_type = false;
  our_pred->side_effects = true; /* Exiting is a side effect... */
  our_pred->no_default_print = false; /* Don't inhibit the default print, though. */
  our_pred->est_success_rate = 1.0f;
  return true;
}


static bool
parse_regextype (const struct parser_table* entry, char **argv, int *arg_ptr)
{
  const char *type_name;
  if (collect_arg (argv, arg_ptr, &type_name))
    {
      /* collect the regex type name */
      options.regex_options = get_regex_type (type_name);
      return parse_noop (entry, argv, arg_ptr);
    }
  return false;
}


static bool
parse_regex (const struct parser_table* entry, char **argv, int *arg_ptr)
{
  return insert_regex (argv, arg_ptr, entry, options.regex_options);
}

static bool
insert_regex (char **argv,
	      int *arg_ptr,
	      const struct parser_table *entry,
	      int regex_options)
{
  const char *rx;
  if (collect_arg (argv, arg_ptr, &rx))
    {
      struct re_pattern_buffer *re;
      const char *error_message;
      struct predicate *our_pred = insert_primary_withpred (entry, pred_regex, rx);
      our_pred->need_stat = our_pred->need_type = false;
      re = xmalloc (sizeof (struct re_pattern_buffer));
      our_pred->args.regex = re;
      re->allocated = 100;
      re->buffer = xmalloc (re->allocated);
      re->fastmap = NULL;

      re_set_syntax (regex_options);
      re->syntax = regex_options;
      re->translate = NULL;

      error_message = re_compile_pattern (rx, strlen (rx), re);
      if (error_message)
        die (EXIT_FAILURE, 0,
	     _("failed to compile regular expression '%s': %s"),
	     rx, error_message);
      our_pred->est_success_rate = estimate_pattern_match_rate (rx, 1);
      return true;
    }
  return false;
}

static bool
parse_size (const struct parser_table* entry, char **argv, int *arg_ptr)
{
  struct predicate *our_pred;
  char *arg;
  uintmax_t num;
  char suffix;
  enum comparison_type c_type;

  int blksize = 512;
  int len;

  /* XXX: cannot (yet) convert to ue collect_arg() as this
   * function modifies the args in-place.
   */
  if ((argv == NULL) || (argv[*arg_ptr] == NULL))
    return false;
  arg = argv[*arg_ptr];

  len = strlen (arg);
  if (len == 0)
    die (EXIT_FAILURE, 0, _("invalid null argument to -size"));

  suffix = arg[len - 1];
  switch (suffix)
    {
    case 'b':
      blksize = 512;
      arg[len - 1] = '\0';
      break;

    case 'c':
      blksize = 1;
      arg[len - 1] = '\0';
      break;

    case 'k':
      blksize = 1024;
      arg[len - 1] = '\0';
      break;

    case 'M':			/* Mebibytes */
      blksize = 1024*1024;
      arg[len - 1] = '\0';
      break;

    case 'G':			/* Gibibytes */
      blksize = 1024*1024*1024;
      arg[len - 1] = '\0';
      break;

    case 'w':
      blksize = 2;
      arg[len - 1] = '\0';
      break;

    case '0':
    case '1':
    case '2':
    case '3':
    case '4':
    case '5':
    case '6':
    case '7':
    case '8':
    case '9':
      suffix = 0;
      break;

    default:
      die (EXIT_FAILURE, 0,
	   _("invalid -size type `%c'"), argv[*arg_ptr][len - 1]);
    }
  /* TODO: accept fractional mebibytes etc. ? */
  if (!get_num (arg, &num, &c_type))
    {
      char tail[2];
      tail[0] = suffix;
      tail[1] = 0;

      die (EXIT_FAILURE, 0,
	   _("Invalid argument `%s%s' to -size"),
	   arg, tail);
      return false;
    }
  our_pred = insert_primary (entry, arg);
  our_pred->args.size.kind = c_type;
  our_pred->args.size.blocksize = blksize;
  our_pred->args.size.size = num;
  our_pred->need_stat = true;
  our_pred->need_type = false;

  if (COMP_GT == c_type)
    our_pred->est_success_rate = (num*blksize > 20480) ? 0.1 : 0.9;
  else if (COMP_LT == c_type)
    our_pred->est_success_rate = (num*blksize > 20480) ? 0.9 : 0.1;
  else
    our_pred->est_success_rate = 0.01;

  (*arg_ptr)++;
  return true;
}


static bool
parse_samefile (const struct parser_table* entry, char **argv, int *arg_ptr)
{
  /* General idea: stat the file, remember device and inode numbers.
   * If a candidate file matches those, it's the same file.
   */
  struct predicate *our_pred;
  struct stat st, fst;
  int fd, openflags;
  const char *filename;

  set_stat_placeholders (&st);
  if (!collect_arg_stat_info (argv, arg_ptr, &st, &filename))
    return false;

  set_stat_placeholders (&fst);
  /* POSIX systems are free to re-use the inode number of a deleted
   * file.  To ensure that we are not fooled by inode reuse, we hold
   * the file open if we can.  This would prevent the system reusing
   * the file.
   */
  fd = -3;			/* -3 means uninitialized */
  openflags = O_RDONLY;

  if (options.symlink_handling == SYMLINK_NEVER_DEREF)
    {
      if (options.open_nofollow_available)
	{
	  assert (O_NOFOLLOW != 0);
	  openflags |= O_NOFOLLOW;
	  fd = -1;		/* safe to open it. */
	}
      else
	{
	  if (S_ISLNK(st.st_mode))
	    {
	      /* no way to ensure that a symlink will not be followed
	       * by open(2), so fall back on using lstat().  Accept
	       * the risk that the named file will be deleted and
	       * replaced with another having the same inode.
	       *
	       * Avoid opening the file.
	       */
	      fd = -2;		/* Do not open it */
	    }
	  else
	    {
	      fd = -1;
	      /* Race condition here: the file might become a symlink here. */
	    }
	}
    }
  else
    {
      /* We want to dereference the symlink anyway */
      fd = -1;			/* safe to open it without O_NOFOLLOW */
    }

  assert (fd != -3);		/* check we made a decision */
  if (fd == -1)
    {
      /* Race condition here.  The file might become a
       * symbolic link in between our call to stat and
       * the call to open_cloexec.
       */
      fd = open_cloexec (filename, openflags);

      if (fd >= 0)
	{
	  /* We stat the file again here to prevent a race condition
	   * between the first stat and the call to open(2).
	   */
	  if (0 != fstat (fd, &fst))
	    {
	      fatal_target_file_error (errno, filename);
	    }
	  else
	    {
	      /* Worry about the race condition.  If the file became a
	       * symlink after our first stat and before our call to
	       * open, fst may contain the stat information for the
	       * destination of the link, not the link itself.
	       */
	      if ((*options.xstat) (filename, &st))
		fatal_target_file_error (errno, filename);

	      if ((options.symlink_handling == SYMLINK_NEVER_DEREF)
		  && (!options.open_nofollow_available))
		{
		  if (S_ISLNK(st.st_mode))
		    {
		      /* We lost the race.  Leave the data in st.  The
		       * file descriptor points to the wrong thing.
		       */
		      close (fd);
		      fd = -1;
		    }
		  else
		    {
		      /* Several possibilities here:
		       * 1. There was no race
		       * 2. The file changed into a symlink after the stat and
		       *    before the open, and then back into a non-symlink
		       *    before the second stat.
		       *
		       * In case (1) there is no problem.  In case (2),
		       * the stat() and fstat() calls will have returned
		       * different data.  O_NOFOLLOW was not available,
		       * so the open() call may have followed a symlink
		       * even if the -P option is in effect.
		       */
		      if ((st.st_dev == fst.st_dev)
			  && (st.st_ino == fst.st_ino))
			{
			  /* No race.  No need to copy fst to st,
			   * since they should be identical (modulo
			   * differences in padding bytes).
			   */
			}
		      else
			{
			  /* We lost the race.  Leave the data in st.  The
			   * file descriptor points to the wrong thing.
			   */
			  close (fd);
			  fd = -1;
			}
		    }
		}
	      else
		{
		  st = fst;
		}
	    }
	}
    }

  our_pred = insert_primary (entry, filename);
  our_pred->args.samefileid.ino = st.st_ino;
  our_pred->args.samefileid.dev = st.st_dev;
  our_pred->args.samefileid.fd  = fd;
  our_pred->need_type = false;
  /* smarter way: compare type and inode number first. */
  /* TODO: maybe optimise this away by being optimistic */
  our_pred->need_stat = true;
  our_pred->est_success_rate = 0.01f;
  return true;
}

#if 0
/* This function is commented out partly because support for it is
 * uneven.
 */
static bool
parse_show_control_chars (const struct parser_table* entry,
			  char **argv,
			  int *arg_ptr)
{
  const char *arg;
  const char *errmsg = _("The -show-control-chars option takes "
			 "a single argument which "
			 "must be 'literal' or 'safe'");

  if ((argv == NULL) || (argv[*arg_ptr] == NULL))
    {
      die (EXIT_FAILURE, errno, "%s", errmsg);
      return false;
    }
  else
    {
      arg = argv[*arg_ptr];

      if (0 == strcmp ("literal", arg))
	{
	  options.literal_control_chars = true;
	}
      else if (0 == strcmp ("safe", arg))
	{
	  options.literal_control_chars = false;
	}
      else
	{
	  die (EXIT_FAILURE, errno, "%s", errmsg);
	  return false;
	}
      (*arg_ptr)++;		/* consume the argument. */
      return true;
    }
}
#endif


static bool
parse_true (const struct parser_table* entry, char **argv, int *arg_ptr)
{
  struct predicate *our_pred;

  (void) argv;
  (void) arg_ptr;

  our_pred = insert_primary_noarg (entry);
  our_pred->need_stat = our_pred->need_type = false;
  our_pred->est_success_rate = 1.0f;
  return true;
}

static bool
parse_noop (const struct parser_table* entry, char **argv, int *arg_ptr)
{
  (void) entry;
  return parse_true (get_noop (), argv, arg_ptr);
}

static bool
parse_accesscheck (const struct parser_table* entry, char **argv, int *arg_ptr)
{
  struct predicate *our_pred;
  (void) argv;
  (void) arg_ptr;
  our_pred = insert_primary_noarg (entry);
  our_pred->need_stat = our_pred->need_type = false;
  our_pred->side_effects = our_pred->no_default_print = false;
  if (pred_is(our_pred, pred_executable))
    our_pred->est_success_rate = 0.2;
  else
    our_pred->est_success_rate = 0.9;
  return true;
}

static bool
parse_type (const struct parser_table* entry, char **argv, int *arg_ptr)
{
  return insert_type (argv, arg_ptr, entry, pred_type);
}

static bool
parse_uid (const struct parser_table* entry, char **argv, int *arg_ptr)
{
  struct predicate *p = insert_num (argv, arg_ptr, entry);
  if (p)
    {
      p->est_success_rate = (p->args.numinfo.l_val < 100) ? 0.99 : 0.2;
      return true;
    }
  else
    {
      --*arg_ptr;		/* don't consume the invalid argument. */
      return false;
    }
}

static bool
parse_used (const struct parser_table* entry, char **argv, int *arg_ptr)
{
  struct predicate *our_pred;
  struct time_val tval;
  const char *offset_str;
  const char *errmsg = "arithmetic overflow while converting %s days to a number of seconds";

  if (collect_arg (argv, arg_ptr, &offset_str))
    {
      /* The timespec is actually a delta value, so we use an origin of 0. */
      struct timespec zero = {0,0};
      if (get_relative_timestamp (offset_str, &tval, zero, DAYSECS, errmsg))
	{
	  our_pred = insert_primary (entry, offset_str);
	  our_pred->args.reftime = tval;
	  our_pred->est_success_rate = estimate_file_age_success_rate (tval.ts.tv_sec / DAYSECS);
	  return true;
	}
      else
	{
	  die (EXIT_FAILURE, 0,
	       _("Invalid argument %s to -used"), offset_str);
	  /*NOTREACHED*/
	  return false;
	}
    }
  else
    {
      return false;		/* missing argument */
    }
}

static bool
parse_user (const struct parser_table* entry, char **argv, int *arg_ptr)
{
  const char *username;

  if (collect_arg (argv, arg_ptr, &username))
    {
      struct predicate *our_pred;
      uid_t uid;
      struct passwd *cur_pwd = getpwnam (username);
      endpwent ();
      if (cur_pwd != NULL)
	{
	  uid = cur_pwd->pw_uid;
	}
      else
	{
	  const size_t uid_len = strspn (username, "0123456789");
	  if (uid_len && (username[uid_len]==0))
	    {
	      uid = safe_atoi (username, options.err_quoting_style);
	    }
	  else
	    {
	      /* This is a fatal error (if we just return false, the caller
	       * will say "invalid argument `username' to -user", which is
	       * not as helpful). */
	      if (username[0])
		{
		  die (EXIT_FAILURE, 0,
		       _("%s is not the name of a known user"),
		       quotearg_n_style (0, options.err_quoting_style,
					 username));
		}
	      else
		{
		  die (EXIT_FAILURE, 0,
		       _("The argument to -user should not be empty"));
		}
	      /*NOTREACHED*/
	      return false;
	    }
	}
      our_pred = insert_primary (entry, username);
      our_pred->args.uid = uid;
      our_pred->est_success_rate = (our_pred->args.uid < 100) ? 0.99 : 0.2;
      return true;
    }
  return false;
}

static bool
parse_version (const struct parser_table* entry, char **argv, int *arg_ptr)
{
  bool has_features = false;
  int flags;

  (void) argv;
  (void) arg_ptr;
  (void) entry;

  display_findutils_version ("find");
  printf (_("Features enabled: "));

#if CACHE_IDS
  printf ("CACHE_IDS(ignored) ");
  has_features = true;
#endif
#if defined HAVE_STRUCT_DIRENT_D_TYPE
  printf ("D_TYPE ");
  has_features = true;
#endif
#if defined O_NOFOLLOW
  printf ("O_NOFOLLOW(%s) ",
	  (options.open_nofollow_available ? "enabled" : "disabled"));
  has_features = true;
#endif
#if defined LEAF_OPTIMISATION
  printf ("LEAF_OPTIMISATION ");
  has_features = true;
#endif
  if (0 < is_selinux_enabled ())
    {
      printf ("SELINUX ");
      has_features = true;
    }

  flags = 0;
  if (is_fts_enabled (&flags))
    {
      printf ("FTS(");
      has_features = true;

      if (flags & FTS_CWDFD)
	printf ("FTS_CWDFD");
      printf (") ");
    }

  printf ("CBO(level=%d) ", (int)(options.optimisation_level));
  has_features = true;

  if (!has_features)
    {
      /* For the moment, leave this as English in case someone wants
	 to parse these strings. */
      printf ("none");
    }
  printf ("\n");

  exit (EXIT_SUCCESS);
}

static bool
parse_context (const struct parser_table* entry, char **argv, int *arg_ptr)
{
  struct predicate *our_pred;

  if ((argv == NULL) || (argv[*arg_ptr] == NULL))
    return false;

  if (is_selinux_enabled () <= 0)
    {
      die (EXIT_FAILURE, 0,
	   _("invalid predicate -context: SELinux is not enabled."));
      return false;
    }
  our_pred = insert_primary (entry, NULL);
  our_pred->est_success_rate = 0.01f;
  our_pred->need_stat = false;
#ifdef DEBUG
  our_pred->p_name = find_pred_name (pred_context);
#endif /*DEBUG*/
  our_pred->args.scontext = argv[*arg_ptr];

  (*arg_ptr)++;
  return true;
}

static bool
parse_xdev (const struct parser_table* entry, char **argv, int *arg_ptr)
{
  options.stay_on_filesystem = true;
  return parse_noop (entry, argv, arg_ptr);
}

static bool
parse_ignore_race (const struct parser_table* entry, char **argv, int *arg_ptr)
{
  options.ignore_readdir_race = true;
  return parse_noop (entry, argv, arg_ptr);
}

static bool
parse_noignore_race (const struct parser_table* entry, char **argv, int *arg_ptr)
{
  options.ignore_readdir_race = false;
  return parse_noop (entry, argv, arg_ptr);
}

static bool
parse_warn (const struct parser_table* entry, char **argv, int *arg_ptr)
{
  options.warnings = true;
  return parse_noop (entry, argv, arg_ptr);
}

static bool
parse_xtype (const struct parser_table* entry, char **argv, int *arg_ptr)
{
  return insert_type (argv, arg_ptr, entry, pred_xtype);
}

static bool
insert_type (char **argv, int *arg_ptr,
	     const struct parser_table *entry,
	     PRED_FUNC which_pred)
{
  struct predicate *our_pred;
  const char *typeletter;
  const char *pred_string = which_pred == pred_xtype ? "-xtype" : "-type";

  if (! collect_arg (argv, arg_ptr, &typeletter))
    return false;

  if (!*typeletter)
    {
      die (EXIT_FAILURE, 0,
	   _("Arguments to %s should contain at least one letter"),
	   pred_string);
      /*NOTREACHED*/
      return false;
    }

  our_pred = insert_primary_withpred (entry, which_pred, typeletter);
  our_pred->est_success_rate = 0.0;

  /* Figure out if we will need to stat the file, because if we don't
   * need to follow symlinks, we can avoid a stat call by using
   * struct dirent.d_type.
   */
  if (which_pred == pred_xtype)
    {
      our_pred->need_stat = true;
      our_pred->need_type = false;
    }
  else
    {
      our_pred->need_stat = false; /* struct dirent is enough */
      our_pred->need_type = true;
    }

  /* From a real system here are the counts of files by type:
     Type   Count  Fraction
     f    4410884  0.875
     d     464722  0.0922
     l     156662  0.0311
     b       4476  0.000888
     c       2233  0.000443
     s         80  1.59e-05
     p         38  7.54e-06
  */

  for (; *typeletter; )
    {
      unsigned int type_cell;
      float rate = 0.01;

      switch (*typeletter)
      {
      case 'b':			/* block special */
	type_cell = FTYPE_BLK;
	rate = 0.000888f;
	break;
      case 'c':			/* character special */
	type_cell = FTYPE_CHR;
	rate = 0.000443f;
	break;
      case 'd':			/* directory */
	type_cell = FTYPE_DIR;
	rate = 0.0922f;
	break;
      case 'f':			/* regular file */
	type_cell = FTYPE_REG;
	rate = 0.875f;
	break;
      case 'l':			/* symbolic link */
#ifdef S_IFLNK
	type_cell = FTYPE_LNK;
	rate = 0.0311f;
#else
	type_cell = 0;
	die (EXIT_FAILURE, 0,
	     _("%s %c is not supported because symbolic links "
	       "are not supported on the platform find was compiled on."),
	     pred_string, (*typeletter));
#endif
	break;
      case 'p':			/* pipe */
#ifdef S_IFIFO
	type_cell = FTYPE_FIFO;
	rate = 7.554e-6f;
#else
	type_cell = 0;
	die (EXIT_FAILURE, 0,
	     _("%s %c is not supported because FIFOs "
	       "are not supported on the platform find was compiled on."),
	     pred_string, (*typeletter));
#endif
	break;
      case 's':			/* socket */
#ifdef S_IFSOCK
	type_cell = FTYPE_SOCK;
	rate = 1.59e-5f;
#else
	type_cell = 0;
	die (EXIT_FAILURE, 0,
	     _("%s %c is not supported because named sockets "
	       "are not supported on the platform find was compiled on."),
	     pred_string, (*typeletter));
#endif
	break;
      case 'D':			/* Solaris door */
#ifdef S_IFDOOR
	type_cell = FTYPE_DOOR;
	/* There are no Solaris doors on the example system surveyed
	 * above, but if someone uses -type D, they are presumably
	 * expecting to find a non-zero number.  We guess at a
	 * rate. */
	rate = 1.0e-5f;
#else
	type_cell = 0;
	die (EXIT_FAILURE, 0,
	     _("%s %c is not supported because Solaris doors "
	       "are not supported on the platform find was compiled on."),
	     pred_string, (*typeletter));
#endif
	break;
      default:			/* None of the above ... nuke 'em. */
	type_cell = 0;
	die (EXIT_FAILURE, 0,
	     _("Unknown argument to %s: %c"), pred_string, (*typeletter));
	/*NOTREACHED*/
	return false;
      }

      if (our_pred->args.types[type_cell])
	{
	  die (EXIT_FAILURE, 0,
	       _("Duplicate file type '%c' in the argument list to %s."),
	       (*typeletter), pred_string);
	}

      our_pred->est_success_rate += rate;
      our_pred->args.types[type_cell] = true;

      /* Advance.
       * Currently, only 1-character file types separated by ',' are supported.
       */
      typeletter++;
      if (*typeletter)
	{
	  if (*typeletter != ',')
	    {
	      die (EXIT_FAILURE, 0,
		   _("Must separate multiple arguments to %s using: ','"),
		   pred_string);
	      /*NOTREACHED*/
	      return false;
	    }
	  typeletter++;
	  if (!*typeletter)
	    {
	      die (EXIT_FAILURE, 0,
		   _("Last file type in list argument to %s "
		     "is missing, i.e., list is ending on: ','"),
		   pred_string);
	      /*NOTREACHED*/
	      return false;
	    }
	}
    }

  return true;
}


/* Return true if the file accessed via FP is a terminal.
 */
static bool
stream_is_tty (FILE *fp)
{
  int fd = fileno (fp);
  if (-1 == fd)
    {
      return false; /* not a valid stream */
    }
  else
    {
      return isatty (fd) ? true : false;
    }

}






static void
check_path_safety (const char *action)
{
  const char *path = getenv ("PATH");
  const char *path_separators = ":";
  size_t pos, len;

  if (NULL == path)
    {
      /* $PATH is not set.  Assume the OS default is safe.
       * That may not be true on Windows, but I'm not aware
       * of a way to get Windows to avoid searching the
       * current directory anyway.
       */
      return;
    }

  splitstring (path, path_separators, true, &pos, &len);
  do
    {
      if (0 == len || (1 == len && path[pos] == '.'))
	{
	  /* empty field signifies . */
	  die (EXIT_FAILURE, 0,
	       _("The current directory is included in the PATH "
		 "environment variable, which is insecure in "
		 "combination with the %s action of find.  "
		 "Please remove the current directory from your "
		 "$PATH (that is, remove \".\", doubled colons, "
		 "or leading or trailing colons)"),
	       action);
	}
      else if (path[pos] != '/')
	{
	  char *relpath = strndup (&path[pos], len);
	  die (EXIT_FAILURE, 0,
	       _("The relative path %s is included in the PATH "
		 "environment variable, which is insecure in "
		 "combination with the %s action of find.  "
		 "Please remove that entry from $PATH"),
	       safely_quote_err_filename (0, relpath ? relpath : &path[pos]),
	       action);
	  /*NOTREACHED*/
	  free (relpath);
	}
    } while (splitstring (path, path_separators, false, &pos, &len));
}


/* handles both exec and ok predicate */
static bool
insert_exec_ok (const char *action,
		const struct parser_table *entry,
		char **argv,
		int *arg_ptr)
{
  int start, end;		/* Indexes in ARGV of start & end of cmd. */
  int i;			/* Index into cmd args */
  int saw_braces;		/* True if previous arg was '{}'. */
  bool allow_plus;		/* True if + is a valid terminator */
  int brace_count;		/* Number of instances of {}. */
  const char *brace_arg;	/* Which arg did {} appear in? */
  PRED_FUNC func = entry->pred_func;
  enum BC_INIT_STATUS bcstatus;

  struct predicate *our_pred;
  struct exec_val *execp;	/* Pointer for efficiency. */

  if ((argv == NULL) || (argv[*arg_ptr] == NULL))
    return false;

  our_pred = insert_primary_withpred (entry, func, "(some -exec* arguments)");
  our_pred->side_effects = our_pred->no_default_print = true;
  our_pred->need_type = our_pred->need_stat = false;

  execp = &our_pred->args.exec_vec;
  execp->wd_for_exec = NULL;

  if ((func != pred_okdir) && (func != pred_ok))
    {
      allow_plus = true;
      execp->close_stdin = false;
    }
  else
    {
      allow_plus = false;
      /* If find reads stdin (i.e. for -ok and similar), close stdin
       * in the child to prevent some script from consiming the output
       * intended for find.
       */
      execp->close_stdin = true;
    }


  if ((func == pred_execdir) || (func == pred_okdir))
    {
      execp->wd_for_exec = NULL;
      options.ignore_readdir_race = false;
      check_path_safety (action);
    }
  else
    {
      assert (NULL != initial_wd);
      execp->wd_for_exec = initial_wd;
    }

  our_pred->args.exec_vec.multiple = 0;

  /* Count the number of args with path replacements, up until the ';'.
   * Also figure out if the command is terminated by ";" or by "+".
   */
  start = *arg_ptr;
  for (end = start, saw_braces=0, brace_count=0, brace_arg=NULL;
       (argv[end] != NULL)
       && ((argv[end][0] != ';') || (argv[end][1] != '\0'));
       end++)
    {
      /* For -exec and -execdir, "{} +" can terminate the command. */
      if ( allow_plus
	   && argv[end][0] == '+' && argv[end][1] == 0
	   && saw_braces)
	{
	  our_pred->args.exec_vec.multiple = 1;
	  break;
	}

      saw_braces = 0;
      if (mbsstr (argv[end], "{}"))
	{
	  saw_braces = 1;
	  brace_arg = argv[end];
	  ++brace_count;

	  if (0 == end && (func == pred_execdir || func == pred_okdir))
	    {
	      /* The POSIX standard says that {} replacement should
	       * occur even in the utility name.  This is insecure
	       * since it means we will be executing a command whose
	       * name is chosen according to whatever find finds in
	       * the file system.  That can be influenced by an
	       * attacker.  Hence for -execdir and -okdir this is not
	       * allowed.  We can specify this as those options are
	       * not defined by POSIX.
	       */
	      die (EXIT_FAILURE, 0,
		   _("You may not use {} within the utility name for "
		     "-execdir and -okdir, because this is a potential "
		     "security problem."));
	    }
	}
    }

  /* Fail if no command given or no semicolon found. */
  if ((end == start) || (argv[end] == NULL))
    {
      *arg_ptr = end;
      free (our_pred);
      return false;
    }

  if (our_pred->args.exec_vec.multiple)
    {
      const char *suffix;
      if (func == pred_execdir)
	suffix = "dir";
      else
	suffix = "";

      if (brace_count > 1)
	{
	  die (EXIT_FAILURE, 0,
	       _("Only one instance of {} is supported with -exec%s ... +"),
	       suffix);
	}
      else if (strlen (brace_arg) != 2u)
	{
	  enum { MsgBufSize = 19 };
	  char buf[MsgBufSize];
	  const size_t needed = snprintf (buf, MsgBufSize, "-exec%s ... {} +", suffix);
	  assert (needed <= MsgBufSize);  /* If this assertion fails, correct the value of MsgBufSize. */
	  die (EXIT_FAILURE, 0,
	       _("In %s the %s must appear by itself, but you specified %s"),
	       quotearg_n_style (0, options.err_quoting_style, buf),
	       quotearg_n_style (1, options.err_quoting_style, "{}"),
	       quotearg_n_style (2, options.err_quoting_style, brace_arg));
	}
    }

  /* We use a switch statement here so that the compiler warns us when
   * we forget to handle a newly invented enum value.
   *
   * Like xargs, we allow 2KiB of headroom for the launched utility to
   * export its own environment variables before calling something
   * else.
   */
  bcstatus = bc_init_controlinfo (&execp->ctl, 2048u);
  switch (bcstatus)
    {
    case BC_INIT_ENV_TOO_BIG:
    case BC_INIT_CANNOT_ACCOMODATE_HEADROOM:
      die (EXIT_FAILURE, 0, _("The environment is too large for exec()."));
      break;
    case BC_INIT_OK:
      /* Good news.  Carry on. */
      break;
    }
  bc_use_sensible_arg_max (&execp->ctl);


  execp->ctl.exec_callback = launch;

  if (our_pred->args.exec_vec.multiple)
    {
      /* "+" terminator, so we can just append our arguments after the
       * command and initial arguments.
       */
      execp->replace_vec = NULL;
      execp->ctl.replace_pat = NULL;
      execp->ctl.rplen = 0;
      execp->ctl.lines_per_exec = 0; /* no limit */
      execp->ctl.args_per_exec = 0; /* no limit */

      /* remember how many arguments there are */
      execp->ctl.initial_argc = (end-start) - 1;

      /* execp->state = xmalloc(sizeof struct buildcmd_state); */
      bc_init_state (&execp->ctl, &execp->state, execp);

      /* Gather the initial arguments.  Skip the {}. */
      for (i=start; i<end-1; ++i)
	{
	  bc_push_arg (&execp->ctl, &execp->state,
		       argv[i], strlen (argv[i])+1,
		       NULL, 0,
		       1);
	}
    }
  else
    {
      /* Semicolon terminator - more than one {} is supported, so we
       * have to do brace-replacement.
       */
      execp->num_args = end - start;

      execp->ctl.replace_pat = "{}";
      execp->ctl.rplen = strlen (execp->ctl.replace_pat);
      execp->ctl.lines_per_exec = 0; /* no limit */
      execp->ctl.args_per_exec = 0; /* no limit */
      execp->replace_vec = xmalloc (sizeof(char*)*execp->num_args);


      /* execp->state = xmalloc(sizeof(*(execp->state))); */
      bc_init_state (&execp->ctl, &execp->state, execp);

      /* Remember the (pre-replacement) arguments for later. */
      for (i=0; i<execp->num_args; ++i)
	{
	  execp->replace_vec[i] = argv[i+start];
	}
    }

  if (argv[end] == NULL)
    *arg_ptr = end;
  else
    *arg_ptr = end + 1;

  return true;
}



/* Get a timestamp and comparison type.

   STR is the ASCII representation.
   Set *NUM_DAYS to the number of days/minutes/whatever, taken as being
   relative to ORIGIN (usually the current moment or midnight).
   Thus the sense of the comparison type appears to be reversed.
   Set *COMP_TYPE to the kind of comparison that is requested.
   Issue OVERFLOWMESSAGE if overflow occurs.
   Return true if all okay, false if input error.

   Used by -atime, -ctime and -mtime (parsers) to
   get the appropriate information for a time predicate processor. */

static bool
get_relative_timestamp (const char *str,
			struct time_val *result,
			struct timespec origin,
			double sec_per_unit,
			const char *overflowmessage)
{
  double offset, seconds, nanosec;
  static const long nanosec_per_sec = 1000000000;

  if (get_comp_type (&str, &result->kind))
    {
      /* Invert the sense of the comparison */
      switch (result->kind)
	{
	case COMP_LT: result->kind = COMP_GT; break;
	case COMP_GT: result->kind = COMP_LT; break;
	case COMP_EQ:
	  break; /* inversion leaves it unchanged */
	}

      /* Convert the ASCII number into floating-point. */
      if (xstrtod (str, NULL, &offset, strtod))
	{
	  /* Separate the floating point number the user specified
	   * (which is a number of days, or minutes, etc) into an
	   * integral number of seconds (SECONDS) and a fraction (NANOSEC).
	   */
	  nanosec = modf (offset * sec_per_unit, &seconds);
	  nanosec *= 1.0e9;	/* convert from fractional seconds to ns. */
	  assert (nanosec < nanosec_per_sec);

	  /* Perform the subtraction, and then check for overflow.
	   * On systems where signed aritmetic overflow does not
	   * wrap, this check may be unreliable.   The C standard
	   * does not require this approach to work, but I am aware
	   * of no platforms where it fails.
	   */
	  result->ts.tv_sec  = origin.tv_sec - seconds;
	  if ((origin.tv_sec < result->ts.tv_sec) != (seconds < 0))
	    {
	      /* an overflow has occurred. */
	      die (EXIT_FAILURE, 0, overflowmessage, str);
	    }

	  result->ts.tv_nsec = origin.tv_nsec - nanosec;
	  if (origin.tv_nsec < nanosec)
	    {
	      /* Perform a carry operation */
	      result->ts.tv_nsec += nanosec_per_sec;
	      result->ts.tv_sec  -= 1;
	    }
	  return true;
	}
      else
	{
	  /* Conversion from ASCII to double failed. */
	  return false;
	}
    }
  else
    {
      return false;
    }
}

/* Insert a time predicate based on the information in ENTRY.
   ARGV is a pointer to the argument array.
   ARG_PTR is a pointer to an index into the array, incremented if
   all went well.

   Return true if input is valid, false if not.

   A new predicate node is assigned, along with an argument node
   obtained with malloc.

   Used by -atime, -ctime, and -mtime parsers. */

static bool
parse_time (const struct parser_table* entry, char *argv[], int *arg_ptr)
{
  struct predicate *our_pred;
  struct time_val tval;
  enum comparison_type comp;
  const char *timearg, *orig_timearg;
  const char *errmsg = _("arithmetic overflow while converting %s "
			 "days to a number of seconds");
  struct timespec origin;
  const int saved_argc = *arg_ptr;

  if (!collect_arg (argv, arg_ptr, &timearg))
    return false;
  orig_timearg = timearg;

  /* Decide the origin by previewing the comparison type. */
  origin = options.cur_day_start;

  if (get_comp_type (&timearg, &comp))
    {
      /* Remember, we invert the sense of the comparison, so this tests
       * against COMP_LT instead of COMP_GT...
       */
      if (COMP_LT == comp)
	{
	  uintmax_t expected = origin.tv_sec + (DAYSECS-1);
	  origin.tv_sec += (DAYSECS-1);
	  if (expected != (uintmax_t)origin.tv_sec)
	    {
	      die (EXIT_FAILURE, 0,
		   _("arithmetic overflow when trying to calculate the end of today"));
	    }
	}
    }
  /* We discard the value of comp here, as get_relative_timestamp
   * will set tval.kind.  For that to work, we have to restore
   * timearg so that it points to the +/- prefix, if any.  get_comp_type()
   * will have advanced timearg, so we restore it.
   */
  timearg = orig_timearg;

  if (!get_relative_timestamp (timearg, &tval, origin, DAYSECS, errmsg))
    {
      *arg_ptr = saved_argc;	/* don't consume the invalid argument */
      return false;
    }

  our_pred = insert_primary (entry, orig_timearg);
  our_pred->args.reftime = tval;
  our_pred->est_success_rate = estimate_timestamp_success_rate (tval.ts.tv_sec);

  if (options.debug_options & DebugExpressionTree)
    {
      time_t t;

      fprintf (stderr, "inserting %s\n", our_pred->p_name);
      fprintf (stderr, "    type: %s    %s  ",
	       (tval.kind == COMP_GT) ? "gt" :
	       ((tval.kind == COMP_LT) ? "lt" : ((tval.kind == COMP_EQ) ? "eq" : "?")),
	       (tval.kind == COMP_GT) ? " >" :
	       ((tval.kind == COMP_LT) ? " <" : ((tval.kind == COMP_EQ) ? ">=" : " ?")));
      t = our_pred->args.reftime.ts.tv_sec;
      fprintf (stderr, "%"PRIuMAX" %s",
	       (uintmax_t) our_pred->args.reftime.ts.tv_sec,
	       ctime (&t));
      if (tval.kind == COMP_EQ)
	{
	  t = our_pred->args.reftime.ts.tv_sec + DAYSECS;
	  fprintf (stderr, "                 <  %"PRIuMAX" %s",
		   (uintmax_t) t, ctime (&t));
	}
    }

  return true;
}

/* Get the comparison type prefix (if any) from a number argument.
   The prefix is at *STR.
   Set *COMP_TYPE to the kind of comparison that is requested.
   Advance *STR beyond any initial comparison prefix.

   Return true if all okay, false if input error.  */
static bool
get_comp_type (const char **str, enum comparison_type *comp_type)
{
  switch (**str)
    {
    case '+':
      *comp_type = COMP_GT;
      (*str)++;
      break;
    case '-':
      *comp_type = COMP_LT;
      (*str)++;
      break;
    default:
      *comp_type = COMP_EQ;
      break;
    }
  return true;
}





/* Get a number with comparison information.
   The sense of the comparison information is 'normal'; that is,
   '+' looks for a count > than the number and '-' less than.

   STR is the ASCII representation of the number.
   Set *NUM to the number.
   Set *COMP_TYPE to the kind of comparison that is requested.

   Return true if all okay, false if input error.  */

static bool
get_num (const char *str,
	 uintmax_t *num,
	 enum comparison_type *comp_type)
{
  char *pend;

  if (str == NULL)
    return false;

  /* Figure out the comparison type if the caller accepts one. */
  if (comp_type)
    {
      if (!get_comp_type (&str, comp_type))
	return false;
    }

  return xstrtoumax (str, &pend, 10, num, "") == LONGINT_OK;
}

/* Insert a number predicate.
   ARGV is a pointer to the argument array.
   *ARG_PTR is an index into ARGV, incremented if all went well.
   *PRED is the predicate processor to insert.

   Return true if input is valid, false if error.

   A new predicate node is assigned, along with an argument node
   obtained with malloc.

   Used by -inum and -links parsers. */

static struct predicate *
insert_num (char **argv, int *arg_ptr, const struct parser_table *entry)
{
  const char *numstr;

  if (collect_arg (argv, arg_ptr, &numstr))
  {
    uintmax_t num;
    enum comparison_type c_type;

    if (get_num (numstr, &num, &c_type))
      {
	struct predicate *our_pred = insert_primary (entry, numstr);
	our_pred->args.numinfo.kind = c_type;
	our_pred->args.numinfo.l_val = num;

	if (options.debug_options & DebugExpressionTree)
	  {
	    fprintf (stderr, "inserting %s\n", our_pred->p_name);
	    fprintf (stderr, "    type: %s    %s  ",
		     (c_type == COMP_GT) ? "gt" :
		     ((c_type == COMP_LT) ? "lt" : ((c_type == COMP_EQ) ? "eq" : "?")),
		     (c_type == COMP_GT) ? " >" :
		     ((c_type == COMP_LT) ? " <" : ((c_type == COMP_EQ) ? " =" : " ?")));
	    fprintf (stderr, "%"PRIuMAX"\n", our_pred->args.numinfo.l_val);
	  }
	return our_pred;
      }
  }
  return NULL;
}

static void
open_output_file (const char *path, struct format_val *p)
{
  p->segment = NULL;
  p->quote_opts = clone_quoting_options (NULL);

  if (!strcmp (path, "/dev/stderr"))
    {
      p->stream = stderr;
      p->filename = _("standard error");
    }
  else if (!strcmp (path, "/dev/stdout"))
    {
      p->stream = stdout;
      p->filename = _("standard output");
    }
  else
    {
      p->stream = sharefile_fopen (state.shared_files, path);
      p->filename = path;

      if (p->stream == NULL)
	{
	  fatal_nontarget_file_error (errno, path);
	}
    }

  p->dest_is_tty = stream_is_tty (p->stream);
}

static void
open_stdout (struct format_val *p)
{
  open_output_file ("/dev/stdout", p);
}
